From 5a87f3013fdd10df338d63c7beef8e5036cb9ee3 Mon Sep 17 00:00:00 2001 From: alexcastgonz <82875882+pcbeingused333@users.noreply.github.com> Date: Fri, 23 Oct 2026 05:16:08 -0400 Subject: [PATCH 01/60] fix(ai): keep streamed OpenAI tool calls that have no index (#1091) * fix(ai): keep streamed OpenAI tool calls that have no index accumulate_openai_tool_calls skipped every delta without an index, so tool calls from OpenAI-compatible providers that omit it never reached $ai_output. A delta with a new id now opens a call, and one without an id continues the latest call. Co-Authored-By: Claude Sonnet 5.5 * fix(ai): keep synthetic tool call keys apart from provider indexes A call without an index took key max + 1, which a later call with that real index overwrote, merging two calls and dropping one. Index-less calls now get string keys of their own. Co-Authored-By: Claude Sonnet 5.5 * fix(ai): widen the streamed tool call accumulator key type Co-Authored-By: Claude Opus 5.5 * fix(ai): update streamed tool call API snapshot --------- Co-authored-by: Claude Sonnet 5.5 Co-authored-by: Manoel Aranda Neto (cherry picked from commit ffde3c57f26b9daf92f12fbb21edd4f359e4388b) --- .../openai-stream-tool-calls-without-index.md | 5 + posthog/ai/openai/_streaming.py | 4 +- posthog/ai/openai/openai_converter.py | 26 +++- .../test/ai/openai/test_openai_converter.py | 134 +++++++++++++++++- references/public_api_snapshot.txt | 2 +- 5 files changed, 164 insertions(+), 7 deletions(-) create mode 100644 .sampo/changesets/openai-stream-tool-calls-without-index.md diff --git a/.sampo/changesets/openai-stream-tool-calls-without-index.md b/.sampo/changesets/openai-stream-tool-calls-without-index.md new file mode 100644 index 00000000..1bc8537d --- /dev/null +++ b/.sampo/changesets/openai-stream-tool-calls-without-index.md @@ -0,0 +1,5 @@ +--- +pypi/posthog: patch +--- + +Streamed OpenAI-compatible chat completions whose tool call deltas carry no `index` are no longer dropped from `$ai_output`. A delta with a new id starts a call, and one without an id continues the latest call. diff --git a/posthog/ai/openai/_streaming.py b/posthog/ai/openai/_streaming.py index 37637268..cc6baf13 100644 --- a/posthog/ai/openai/_streaming.py +++ b/posthog/ai/openai/_streaming.py @@ -1,7 +1,7 @@ """Sync-neutral state accumulation for OpenAI streaming endpoints.""" from dataclasses import dataclass, field -from typing import Any, Dict, List, Optional +from typing import Any, Dict, List, Optional, Union from ..types import StreamingEventData, TokenUsage from ..utils import merge_usage_stats, _responses_stop_reason @@ -52,7 +52,7 @@ class _ChatCompletionsStreamState: usage_stats: TokenUsage = field(default_factory=lambda: TokenUsage()) output: List[Any] = field(default_factory=list) - _tool_calls: Dict[int, Dict[str, Any]] = field(default_factory=dict) + _tool_calls: Dict[Union[int, str], Dict[str, Any]] = field(default_factory=dict) model: Optional[str] = None stop_reason: Optional[str] = None service_tier: Optional[str] = None diff --git a/posthog/ai/openai/openai_converter.py b/posthog/ai/openai/openai_converter.py index e0f397ff..78487d19 100644 --- a/posthog/ai/openai/openai_converter.py +++ b/posthog/ai/openai/openai_converter.py @@ -6,7 +6,7 @@ Chat Completions API and Responses API formats. """ -from typing import Any, Dict, List, Optional, cast +from typing import Any, Dict, List, Optional, Union, cast from posthog.ai.media import to_plain from posthog.ai.types import ( @@ -747,7 +747,7 @@ def extract_openai_tool_calls_from_chunk(chunk: Any) -> Optional[List[Dict[str, def accumulate_openai_tool_calls( - accumulated_tool_calls: Dict[int, Dict[str, Any]], + accumulated_tool_calls: Dict[Union[int, str], Dict[str, Any]], chunk_tool_calls: List[Dict[str, Any]], ) -> None: """ @@ -756,6 +756,8 @@ def accumulate_openai_tool_calls( OpenAI sends tool calls incrementally: - First chunk has id, type, function.name and partial function.arguments - Subsequent chunks have more function.arguments + Deltas without an index are matched by id, start a call when they carry an id + or a function name, and otherwise continue the latest call. Args: accumulated_tool_calls: Dictionary mapping index to accumulated tool call data @@ -764,7 +766,25 @@ def accumulate_openai_tool_calls( for tool_call_delta in chunk_tool_calls: index = tool_call_delta.get("index") if index is None: - continue + # Some OpenAI-compatible providers omit the index. A delta with a new id + # or a function name starts a call, and one with neither continues the + # latest call. Those calls get keys of their own, so they can never + # collide with an index the provider sends later. + call_id = tool_call_delta.get("id") + index = next( + ( + key + for key, tc in accumulated_tool_calls.items() + if call_id and tc["id"] == call_id + ), + None, + ) + if index is None: + has_name = bool((tool_call_delta.get("function") or {}).get("name")) + if accumulated_tool_calls and not call_id and not has_name: + index = next(reversed(accumulated_tool_calls)) + else: + index = f"no-index-{len(accumulated_tool_calls)}" # Initialize tool call if first time seeing this index if index not in accumulated_tool_calls: diff --git a/posthog/test/ai/openai/test_openai_converter.py b/posthog/test/ai/openai/test_openai_converter.py index a6ee44e6..2034f533 100644 --- a/posthog/test/ai/openai/test_openai_converter.py +++ b/posthog/test/ai/openai/test_openai_converter.py @@ -9,8 +9,12 @@ except ImportError: OPENAI_AVAILABLE = False -from posthog.ai.openai._streaming import _ResponsesStreamState +from posthog.ai.openai._streaming import ( + _ChatCompletionsStreamState, + _ResponsesStreamState, +) from posthog.ai.openai.openai_converter import ( + accumulate_openai_tool_calls, extract_openai_stop_reason, format_openai_input, ) @@ -288,3 +292,131 @@ def test_responses_stream_records_every_terminal_stop_reason( ) assert state.stop_reason == expected + + +def _tool_call_chunk(*tool_calls): + return types.SimpleNamespace( + choices=[ + types.SimpleNamespace( + delta=types.SimpleNamespace(content=None, tool_calls=list(tool_calls)), + finish_reason=None, + ) + ], + usage=None, + ) + + +def _tool_call_delta(index=None, id=None, name=None, arguments=None): + return types.SimpleNamespace( + index=index, + id=id, + type="function" if id else None, + function=types.SimpleNamespace(name=name, arguments=arguments), + ) + + +def test_stream_tool_calls_without_index_are_kept(): + # Some OpenAI-compatible providers send each call whole, with no index. + state = _ChatCompletionsStreamState() + state.process_chunk( + _tool_call_chunk(_tool_call_delta(id="call_1", name="a", arguments="{}")) + ) + state.process_chunk( + _tool_call_chunk(_tool_call_delta(id="call_2", name="b", arguments='{"x": 1}')) + ) + + assert [(tc["id"], tc["function"]["name"]) for tc in state.tool_calls] == [ + ("call_1", "a"), + ("call_2", "b"), + ] + assert state.tool_calls[1]["function"]["arguments"] == '{"x": 1}' + + +def test_stream_tool_calls_without_index_join_argument_fragments(): + tool_calls = {} + accumulate_openai_tool_calls( + tool_calls, [{"id": "call_1", "function": {"name": "a", "arguments": '{"q":'}}] + ) + accumulate_openai_tool_calls(tool_calls, [{"function": {"arguments": ' "hi"}'}}]) + accumulate_openai_tool_calls( + tool_calls, [{"id": "call_1", "function": {"arguments": ""}}] + ) + + assert list(tool_calls.values()) == [ + { + "id": "call_1", + "type": "function", + "function": {"name": "a", "arguments": '{"q": "hi"}'}, + } + ] + + +def test_stream_tool_calls_with_index_are_unchanged(): + tool_calls = {} + accumulate_openai_tool_calls( + tool_calls, + [ + {"index": 0, "id": "call_1", "function": {"name": "a", "arguments": "{"}}, + {"index": 1, "id": "call_2", "function": {"name": "b", "arguments": "{}"}}, + ], + ) + accumulate_openai_tool_calls( + tool_calls, [{"index": 0, "function": {"arguments": "}"}}] + ) + + assert tool_calls[0]["function"]["arguments"] == "{}" + assert tool_calls[1]["function"]["name"] == "b" + + +def test_stream_tool_calls_without_index_or_id_start_a_call_per_name(): + tool_calls = {} + accumulate_openai_tool_calls( + tool_calls, [{"function": {"name": "a", "arguments": '{"x": 1}'}}] + ) + accumulate_openai_tool_calls( + tool_calls, [{"function": {"name": "b", "arguments": '{"y": 2}'}}] + ) + + assert [ + (tc["function"]["name"], tc["function"]["arguments"]) + for tc in tool_calls.values() + ] == [("a", '{"x": 1}'), ("b", '{"y": 2}')] + + +def test_indexless_call_does_not_corrupt_later_indexed_call(): + # A call without an index must not take a key that a later real index reuses. + tool_calls = {} + accumulate_openai_tool_calls( + tool_calls, [{"id": "call_b", "function": {"name": "b", "arguments": "{}"}}] + ) + accumulate_openai_tool_calls( + tool_calls, + [{"index": 0, "id": "call_a", "function": {"name": "a", "arguments": "{}"}}], + ) + + assert [(tc["id"], tc["function"]["arguments"]) for tc in tool_calls.values()] == [ + ("call_b", "{}"), + ("call_a", "{}"), + ] + + +def test_indexless_calls_keep_their_order_around_indexed_ones(): + tool_calls = {} + accumulate_openai_tool_calls( + tool_calls, + [ + {"index": 0, "id": "call_a", "function": {"name": "a", "arguments": "{"}}, + {"id": "call_b", "function": {"name": "b", "arguments": "{}"}}, + {"index": 0, "function": {"arguments": "}"}}, + {"id": "call_c", "function": {"name": "c", "arguments": "{}"}}, + ], + ) + + assert [ + (tc["id"], tc["function"]["name"], tc["function"]["arguments"]) + for tc in tool_calls.values() + ] == [ + ("call_a", "a", "{}"), + ("call_b", "b", "{}"), + ("call_c", "c", "{}"), + ] diff --git a/references/public_api_snapshot.txt b/references/public_api_snapshot.txt index 977871ce..bfed0cfe 100644 --- a/references/public_api_snapshot.txt +++ b/references/public_api_snapshot.txt @@ -1361,7 +1361,7 @@ function posthog.ai.media.bytes_to_base64(data: bytes) -> str function posthog.ai.media.ensure_serializable(obj: Any) -> Any function posthog.ai.media.normalize_part_keys(d: dict) -> dict function posthog.ai.media.to_plain(obj: Any) -> Any -function posthog.ai.openai.openai_converter.accumulate_openai_tool_calls(accumulated_tool_calls: Dict[int, Dict[str, Any]], chunk_tool_calls: List[Dict[str, Any]]) -> None +function posthog.ai.openai.openai_converter.accumulate_openai_tool_calls(accumulated_tool_calls: Dict[Union[int, str], Dict[str, Any]], chunk_tool_calls: List[Dict[str, Any]]) -> None function posthog.ai.openai.openai_converter.extract_openai_content_from_chunk(chunk: Any, provider_type: str = 'chat') -> Optional[Any] function posthog.ai.openai.openai_converter.extract_openai_stop_reason(response: Any) -> Optional[str] function posthog.ai.openai.openai_converter.extract_openai_tool_calls_from_chunk(chunk: Any) -> Optional[List[Dict[str, Any]]] From 98187af70a9e4a4e4e7d54ca791292e06e18d79a Mon Sep 17 00:00:00 2001 From: "releaser-posthog-python[bot]" <262414804+releaser-posthog-python[bot]@users.noreply.github.com> Date: Fri, 23 Oct 2026 09:17:44 +0000 Subject: [PATCH 02/60] chore: Release v7.75.7 [skip ci] (cherry picked from commit 7f4c9be1c8aa66bf55cb2080cf0775ff4ec28cff) --- .sampo/changesets/openai-stream-tool-calls-without-index.md | 5 ----- openfeature-provider/CHANGELOG.md | 6 ++++++ openfeature-provider/pyproject.toml | 2 +- posthog/CHANGELOG.md | 6 ++++++ posthog/version.py | 2 +- pyproject.toml | 2 +- uv.lock | 4 ++-- 7 files changed, 17 insertions(+), 10 deletions(-) delete mode 100644 .sampo/changesets/openai-stream-tool-calls-without-index.md diff --git a/.sampo/changesets/openai-stream-tool-calls-without-index.md b/.sampo/changesets/openai-stream-tool-calls-without-index.md deleted file mode 100644 index 1bc8537d..00000000 --- a/.sampo/changesets/openai-stream-tool-calls-without-index.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -pypi/posthog: patch ---- - -Streamed OpenAI-compatible chat completions whose tool call deltas carry no `index` are no longer dropped from `$ai_output`. A delta with a new id starts a call, and one without an id continues the latest call. diff --git a/openfeature-provider/CHANGELOG.md b/openfeature-provider/CHANGELOG.md index 2d706fe7..1c3d0efc 100644 --- a/openfeature-provider/CHANGELOG.md +++ b/openfeature-provider/CHANGELOG.md @@ -4,6 +4,12 @@ All notable changes to `openfeature-provider-posthog` are documented here. This file is maintained by [Sampo](https://github.com/bruits/sampo) from changesets in `.sampo/changesets/` that target `pypi/openfeature-provider-posthog`. +## 0.1.121 — 2026-10-23 + +### Patch changes + +- Updated dependencies: posthog@7.75.7 + ## 0.1.120 — 2026-10-22 ### Patch changes diff --git a/openfeature-provider/pyproject.toml b/openfeature-provider/pyproject.toml index c0bec33a..fa8e6cfc 100644 --- a/openfeature-provider/pyproject.toml +++ b/openfeature-provider/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "openfeature-provider-posthog" -version = "0.1.120" +version = "0.1.121" description = "Official PostHog provider for the OpenFeature Python SDK." readme = "README.md" authors = [{ name = "PostHog", email = "engineering@posthog.com" }] diff --git a/posthog/CHANGELOG.md b/posthog/CHANGELOG.md index 0219635e..ef480700 100644 --- a/posthog/CHANGELOG.md +++ b/posthog/CHANGELOG.md @@ -1,5 +1,11 @@ # posthog +## 7.75.7 — 2026-10-23 + +### Patch changes + +- [ffde3c5](https://github.com/posthog/posthog-python/commit/ffde3c57f26b9daf92f12fbb21edd4f359e4388b) Streamed OpenAI-compatible chat completions whose tool call deltas carry no `index` are no longer dropped from `$ai_output`. A delta with a new id starts a call, and one without an id continues the latest call. — Thanks @pcbeingused333! + ## 7.75.6 — 2026-10-22 ### Patch changes diff --git a/posthog/version.py b/posthog/version.py index 4121f417..49289dc3 100644 --- a/posthog/version.py +++ b/posthog/version.py @@ -1 +1 @@ -VERSION = "7.75.6" +VERSION = "7.75.7" diff --git a/pyproject.toml b/pyproject.toml index 3d1118a7..3248536f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "posthog" -version = "7.75.6" +version = "7.75.7" description = "Integrate PostHog into any python application." authors = [{ name = "PostHog", email = "engineering@posthog.com" }] maintainers = [{ name = "PostHog", email = "engineering@posthog.com" }] diff --git a/uv.lock b/uv.lock index 5b6e2994..51344c2f 100644 --- a/uv.lock +++ b/uv.lock @@ -2656,7 +2656,7 @@ wheels = [ [[package]] name = "openfeature-provider-posthog" -version = "0.1.120" +version = "0.1.121" source = { editable = "openfeature-provider" } dependencies = [ { name = "openfeature-sdk" }, @@ -3015,7 +3015,7 @@ wheels = [ [[package]] name = "posthog" -version = "7.75.6" +version = "7.75.7" source = { editable = "." } dependencies = [ { name = "distro" }, From bb3bc61dee09aeee2d3049a642dde42859c4a07c Mon Sep 17 00:00:00 2001 From: "releaser-posthog-python[bot]" <262414804+releaser-posthog-python[bot]@users.noreply.github.com> Date: Fri, 23 Oct 2026 09:17:49 +0000 Subject: [PATCH 03/60] Update generated references (cherry picked from commit b5eb2debabc7bc6e4e5d033b923d1eef6a6508a6) --- .../posthog-python-references-7.75.7.json | 3548 +++++++++++++++++ .../posthog-python-references-latest.json | 2 +- 2 files changed, 3549 insertions(+), 1 deletion(-) create mode 100644 references/posthog-python-references-7.75.7.json diff --git a/references/posthog-python-references-7.75.7.json b/references/posthog-python-references-7.75.7.json new file mode 100644 index 00000000..e6b6c343 --- /dev/null +++ b/references/posthog-python-references-7.75.7.json @@ -0,0 +1,3548 @@ +{ + "id": "posthog-python", + "hogRef": "0.3", + "info": { + "version": "7.75.7", + "id": "posthog-python", + "title": "PostHog Python SDK", + "description": "Integrate PostHog into any python application.", + "slugPrefix": "posthog-python", + "specUrl": "https://github.com/PostHog/posthog-python" + }, + "types": [ + { + "id": "FeatureFlag", + "name": "FeatureFlag", + "path": "posthog.types.FeatureFlag", + "properties": [ + { + "name": "key", + "type": "str", + "description": "Field: key" + }, + { + "name": "enabled", + "type": "bool", + "description": "Field: enabled" + }, + { + "name": "variant", + "type": "Optional[str]", + "description": "Field: variant" + }, + { + "name": "reason", + "type": "Optional[FlagReason]", + "description": "Field: reason" + }, + { + "name": "metadata", + "type": "Union[FlagMetadata, LegacyFlagMetadata]", + "description": "Field: metadata" + } + ], + "example": "" + }, + { + "id": "FeatureFlagResult", + "name": "FeatureFlagResult", + "path": "posthog.types.FeatureFlagResult", + "properties": [ + { + "name": "key", + "type": "str", + "description": "Field: key" + }, + { + "name": "enabled", + "type": "bool", + "description": "Field: enabled" + }, + { + "name": "variant", + "type": "Optional[str]", + "description": "Field: variant" + }, + { + "name": "payload", + "type": "Optional[Any]", + "description": "Field: payload" + }, + { + "name": "reason", + "type": "Optional[str]", + "description": "Field: reason" + } + ], + "example": "" + }, + { + "id": "FlagMetadata", + "name": "FlagMetadata", + "path": "posthog.types.FlagMetadata", + "properties": [ + { + "name": "id", + "type": "int", + "description": "Field: id" + }, + { + "name": "payload", + "type": "Optional[str]", + "description": "Field: payload" + }, + { + "name": "version", + "type": "int", + "description": "Field: version" + }, + { + "name": "description", + "type": "str", + "description": "Field: description" + }, + { + "name": "has_experiment", + "type": "Optional[bool]", + "description": "Field: has_experiment" + } + ], + "example": "" + }, + { + "id": "FlagReason", + "name": "FlagReason", + "path": "posthog.types.FlagReason", + "properties": [ + { + "name": "code", + "type": "str", + "description": "Field: code" + }, + { + "name": "condition_index", + "type": "Optional[int]", + "description": "Field: condition_index" + }, + { + "name": "description", + "type": "str", + "description": "Field: description" + } + ], + "example": "" + }, + { + "id": "FlagsAndPayloads", + "name": "FlagsAndPayloads", + "path": "posthog.types.FlagsAndPayloads", + "properties": [ + { + "name": "featureFlags", + "type": "Optional[dict[str, Union[bool, str]]]", + "description": "Field: featureFlags" + }, + { + "name": "featureFlagPayloads", + "type": "Optional[dict[str, Any]]", + "description": "Field: featureFlagPayloads" + } + ], + "example": "" + }, + { + "id": "FlagsResponse", + "name": "FlagsResponse", + "path": "posthog.types.FlagsResponse", + "properties": [ + { + "name": "flags", + "type": "dict[str, FeatureFlag]", + "description": "Field: flags" + }, + { + "name": "errorsWhileComputingFlags", + "type": "bool", + "description": "Field: errorsWhileComputingFlags" + }, + { + "name": "requestId", + "type": "str", + "description": "Field: requestId" + }, + { + "name": "quotaLimit", + "type": "Optional[list[str]]", + "description": "Field: quotaLimit" + }, + { + "name": "evaluatedAt", + "type": "Optional[int]", + "description": "Field: evaluatedAt" + }, + { + "name": "minimalFlagCalledEvents", + "type": "bool", + "description": "Field: minimalFlagCalledEvents" + } + ], + "example": "" + }, + { + "id": "LegacyFlagMetadata", + "name": "LegacyFlagMetadata", + "path": "posthog.types.LegacyFlagMetadata", + "properties": [ + { + "name": "payload", + "type": "Any", + "description": "Field: payload" + } + ], + "example": "" + }, + { + "id": "SendFeatureFlagsOptions", + "name": "SendFeatureFlagsOptions", + "path": "posthog.types.SendFeatureFlagsOptions", + "properties": [ + { + "name": "should_send", + "type": "bool", + "description": "Field: should_send" + }, + { + "name": "only_evaluate_locally", + "type": "Optional[bool]", + "description": "Field: only_evaluate_locally" + }, + { + "name": "person_properties", + "type": "Optional[dict[str, Any]]", + "description": "Field: person_properties" + }, + { + "name": "group_properties", + "type": "Optional[dict[str, dict[str, Any]]]", + "description": "Field: group_properties" + }, + { + "name": "flag_keys_filter", + "type": "Optional[list[str]]", + "description": "Field: flag_keys_filter" + } + ], + "example": "" + }, + { + "id": "OptionalCaptureArgs", + "name": "OptionalCaptureArgs", + "path": "posthog.args.OptionalCaptureArgs", + "properties": [ + { + "name": "distinct_id", + "type": "NotRequired[Union[Number, str, UUID, int, any]]", + "description": "Field: distinct_id" + }, + { + "name": "properties", + "type": "NotRequired[Optional[dict[str, Any]]]", + "description": "Field: properties" + }, + { + "name": "timestamp", + "type": "NotRequired[Union[datetime, str, any]]", + "description": "Field: timestamp" + }, + { + "name": "uuid", + "type": "NotRequired[Union[str, UUID, any]]", + "description": "Field: uuid" + }, + { + "name": "groups", + "type": "NotRequired[Optional[dict[str, str]]]", + "description": "Field: groups" + }, + { + "name": "flags", + "type": "NotRequired[Optional[ForwardRef('FeatureFlagEvaluations')]]", + "description": "Field: flags" + }, + { + "name": "send_feature_flags", + "type": "NotRequired[Union[bool, SendFeatureFlagsOptions, any]]", + "description": "Field: send_feature_flags" + }, + { + "name": "disable_geoip", + "type": "NotRequired[Optional[bool]]", + "description": "Field: disable_geoip" + }, + { + "name": "_property_allowlist", + "type": "NotRequired[Optional[frozenset[str]]]", + "description": "Field: _property_allowlist" + } + ], + "example": "" + }, + { + "id": "OptionalSetArgs", + "name": "OptionalSetArgs", + "path": "posthog.args.OptionalSetArgs", + "properties": [ + { + "name": "distinct_id", + "type": "NotRequired[Union[Number, str, UUID, int, any]]", + "description": "Field: distinct_id" + }, + { + "name": "properties", + "type": "NotRequired[Optional[dict[str, Any]]]", + "description": "Field: properties" + }, + { + "name": "timestamp", + "type": "NotRequired[Union[datetime, str, any]]", + "description": "Field: timestamp" + }, + { + "name": "uuid", + "type": "NotRequired[Union[str, UUID, any]]", + "description": "Field: uuid" + }, + { + "name": "disable_geoip", + "type": "NotRequired[Optional[bool]]", + "description": "Field: disable_geoip" + } + ], + "example": "" + }, + { + "id": "SendFeatureFlagsOptions", + "name": "SendFeatureFlagsOptions", + "path": "posthog.types.SendFeatureFlagsOptions", + "properties": [ + { + "name": "should_send", + "type": "bool", + "description": "Field: should_send" + }, + { + "name": "only_evaluate_locally", + "type": "Optional[bool]", + "description": "Field: only_evaluate_locally" + }, + { + "name": "person_properties", + "type": "Optional[dict[str, Any]]", + "description": "Field: person_properties" + }, + { + "name": "group_properties", + "type": "Optional[dict[str, dict[str, Any]]]", + "description": "Field: group_properties" + }, + { + "name": "flag_keys_filter", + "type": "Optional[list[str]]", + "description": "Field: flag_keys_filter" + } + ], + "example": "" + } + ], + "classes": [ + { + "id": "PostHog", + "title": "PostHog", + "description": "This is the SDK reference for the PostHog Python SDK. You can learn more about example usage in the [Python SDK documentation](/docs/libraries/python). You can also follow [Flask](/docs/libraries/flask) and [Django](/docs/libraries/django) guides to integrate PostHog into your project. For long-running applications, create one client during application startup and reuse it for the lifetime of the process. This keeps background queues predictable and makes shutdown flushing straightforward. Multiple clients are still supported for intentional multi-project or multi-host setups.", + "functions": [ + { + "id": "__init__", + "title": "Client", + "description": "Initialize a new PostHog client instance.", + "details": "", + "category": "Initialization", + "params": [ + { + "name": "project_api_key", + "description": "PostHog project API key/token.", + "isOptional": true, + "type": "str" + }, + { + "name": "host", + "description": "PostHog host. Defaults to the US ingestion endpoint when not set. App hosts such as ``https://us.posthog.com`` are mapped to the corresponding ingestion host.", + "isOptional": false, + "type": "any" + }, + { + "name": "debug", + "description": "Enable verbose SDK logging and re-raise errors from public API methods.", + "isOptional": false, + "type": "bool" + }, + { + "name": "max_queue_size", + "description": "Maximum number of events buffered before upload.", + "isOptional": false, + "type": "int" + }, + { + "name": "send", + "description": "If False, queueing succeeds but events are not sent.", + "isOptional": false, + "type": "bool" + }, + { + "name": "on_error", + "description": "Optional callback invoked by background consumers when an upload fails. Keep it short and non-blocking. Calling lifecycle methods directly is safe and deferred, but do not start another thread or task that calls ``flush()``, ``join()``, or ``shutdown()`` and then wait for it from the callback.", + "isOptional": false, + "type": "any" + }, + { + "name": "flush_at", + "description": "Number of queued events that triggers a batch upload.", + "isOptional": false, + "type": "int" + }, + { + "name": "flush_interval", + "description": "Maximum seconds a background consumer waits before flushing a partial batch.", + "isOptional": false, + "type": "float" + }, + { + "name": "gzip", + "description": "Whether to gzip event upload payloads.", + "isOptional": false, + "type": "bool" + }, + { + "name": "max_retries", + "description": "Number of upload retries. Values below 0 are treated as 0.", + "isOptional": false, + "type": "int" + }, + { + "name": "sync_mode", + "description": "If True, send each event synchronously instead of using background worker threads. This blocks the calling thread; in asyncio applications such as FastAPI, use ``AsyncPosthog`` instead.", + "isOptional": false, + "type": "bool" + }, + { + "name": "timeout", + "description": "HTTP request timeout in seconds for event uploads.", + "isOptional": false, + "type": "float" + }, + { + "name": "thread", + "description": "Number of background consumer threads.", + "isOptional": false, + "type": "int" + }, + { + "name": "poll_interval", + "description": "Seconds between local feature flag definition refreshes.", + "isOptional": false, + "type": "int" + }, + { + "name": "personal_api_key", + "description": "Deprecated alias for ``secret_key``. Still honored for backwards compatibility; prefer ``secret_key``, which also accepts a Project Secret API Key.", + "isOptional": false, + "type": "any" + }, + { + "name": "disabled", + "description": "If True, disable captures and API requests. Useful in tests.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable server-side GeoIP enrichment. Defaults to True.", + "isOptional": false, + "type": "bool" + }, + { + "name": "is_server", + "description": "Whether events are emitted from a server-side runtime. Defaults to True; set to False when using the SDK as a client/CLI so the device OS is attributed to the person normally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "historical_migration", + "description": "Mark events as historical migration imports.", + "isOptional": false, + "type": "bool" + }, + { + "name": "feature_flags_request_timeout_seconds", + "description": "Timeout in seconds for feature flag and remote config requests.", + "isOptional": false, + "type": "float" + }, + { + "name": "feature_flags_request_max_retries", + "description": "Number of retries for feature flag requests after network, transport, or timeout failures. Defaults to 1. Set to 0 to disable retries.", + "isOptional": false, + "type": "int" + }, + { + "name": "super_properties", + "description": "Properties merged into every captured event.", + "isOptional": false, + "type": "any" + }, + { + "name": "enable_exception_autocapture", + "description": "Automatically capture uncaught exceptions.", + "isOptional": false, + "type": "bool" + }, + { + "name": "log_captured_exceptions", + "description": "Also log exceptions captured by error tracking.", + "isOptional": false, + "type": "bool" + }, + { + "name": "project_root", + "description": "Root path used to determine in-app stack frames for captured exceptions. Defaults to the current working directory.", + "isOptional": false, + "type": "any" + }, + { + "name": "privacy_mode", + "description": "For AI observability, capture usage metadata without prompt inputs or outputs.", + "isOptional": false, + "type": "bool" + }, + { + "name": "before_send", + "description": "Optional callback that can modify or drop events before upload. Return ``None`` to drop an event.", + "isOptional": false, + "type": "any" + }, + { + "name": "flag_fallback_cache_url", + "description": "Optional feature flag fallback cache URL, such as ``memory://local/?ttl=300&size=10000`` or a Redis URL.", + "isOptional": false, + "type": "any" + }, + { + "name": "enable_local_evaluation", + "description": "Whether to poll feature flag definitions for local evaluation when a personal API key is configured.", + "isOptional": false, + "type": "bool" + }, + { + "name": "flag_definition_cache_provider", + "description": "Optional external cache provider for sharing feature flag definitions across workers.", + "isOptional": true, + "type": "FlagDefinitionCacheProvider" + }, + { + "name": "capture_exception_code_variables", + "description": "Capture local variable values on exception stack frames.", + "isOptional": false, + "type": "bool" + }, + { + "name": "code_variables_mask_patterns", + "description": "Variable-name patterns to mask when capturing code variables.", + "isOptional": false, + "type": "any" + }, + { + "name": "code_variables_ignore_patterns", + "description": "Variable-name patterns to omit when capturing code variables.", + "isOptional": false, + "type": "any" + }, + { + "name": "code_variables_mask_url_credentials", + "description": "Scrub credentials embedded in URLs/DSNs (e.g. ``user:pass@host``) from captured code variables, regardless of the surrounding variable name. Defaults to True.", + "isOptional": false, + "type": "any" + }, + { + "name": "code_variables_detect_secrets", + "description": "Last-resort entropy-based detection that redacts high-entropy secret-looking values (API keys, tokens, strong passwords) sitting in innocuously-named variables, after the name and URL checks. Skips structured ids (UUIDs, ObjectIds, hashes). Defaults to True.", + "isOptional": false, + "type": "any" + }, + { + "name": "in_app_modules", + "description": "Module/package prefixes treated as in-app frames in captured exceptions.", + "isOptional": false, + "type": "UnionType[list[str], any]" + }, + { + "name": "enable_exception_autocapture_rate_limiting", + "description": "Rate limit autocaptured exceptions client-side with a token bucket per exception type. Disabled by default.", + "isOptional": false, + "type": "bool" + }, + { + "name": "exception_autocapture_bucket_size", + "description": "Maximum burst of autocaptured exceptions allowed per exception type (token bucket size, clamped to 0-100).", + "isOptional": false, + "type": "int" + }, + { + "name": "exception_autocapture_refill_rate", + "description": "Tokens restored per refill interval for each exception type's bucket.", + "isOptional": false, + "type": "int" + }, + { + "name": "exception_autocapture_refill_interval_seconds", + "description": "Seconds between token refills for autocaptured exception rate limiting.", + "isOptional": false, + "type": "int" + }, + { + "name": "capture_mode", + "description": "Capture wire protocol to use. Defaults to ``CaptureMode.V0`` (legacy ``/batch/``). Set ``CaptureMode.V1`` (or pass the string ``\"v1\"``) to opt into ``/i/v1/analytics/events``. When omitted, the ``POSTHOG_CAPTURE_MODE`` env var is consulted, then ``V0``.", + "isOptional": false, + "type": "CaptureMode" + }, + { + "name": "capture_compression", + "description": "Request-body compression for capture-v1 uploads (ignored in V0, which uses ``gzip``). ``CaptureCompression.GZIP`` or ``DEFLATE`` (or the strings ``\"gzip\"``/``\"deflate\"``). When omitted, the ``POSTHOG_CAPTURE_COMPRESSION`` env var is consulted, then the legacy ``gzip`` flag, then no compression.", + "isOptional": false, + "type": "CaptureCompression" + }, + { + "name": "secret_key", + "description": "A Personal API Key or Project Secret API Key, used to authenticate local feature flag evaluation, remote config payloads, and decrypted flag payloads. Example:: posthog.Client(project_api_key, secret_key=\"phx_...\")", + "isOptional": false, + "type": "any" + }, + { + "name": "metrics", + "description": "", + "isOptional": true, + "type": "dict" + }, + { + "name": "enable_full_ai_capture", + "description": "Route PostHog AI wrapper events through the dedicated AI capture endpoint and capture full AI content: skips string truncation and passes media (base64/data URIs) through unredacted. ``privacy_mode`` always wins. Defaults to False.", + "isOptional": false, + "type": "bool" + }, + { + "name": "capture_trace_context", + "description": "When OpenTelemetry is installed and a valid span is active at capture time, add its trace and span IDs as ``$trace_id`` and ``$span_id`` properties to events captured with ``capture()`` and ``capture_ai()``, so they can be correlated with backend traces. Explicit ``$trace_id``/``$span_id`` values passed in ``properties`` win. Exception events (``capture_exception``) always attach these IDs regardless of this setting. Defaults to False.", + "isOptional": false, + "type": "bool" + }, + { + "name": "_use_ai_lane", + "description": "", + "isOptional": false, + "type": "bool" + }, + { + "name": "_enable_multimodal_capture", + "description": "", + "isOptional": false, + "type": "bool" + }, + { + "name": "traces", + "description": "Config dict for distributed tracing: ``service_name``, ``service_version``, ``environment``, ``resource_attributes``, ``flush_interval`` (5 s), ``max_queue_size`` (2048), ``max_export_batch_size`` (512), ``max_live_spans`` (10000), ``max_span_age`` (3600 s), ``max_attributes_per_span`` (128), ``max_events_per_span`` (128), ``max_attribute_value_length`` (8192). ``before_span_send`` is a callable, or a list run in order, that receives each finished span as a dict (``trace_id``, ``span_id`` and ``parent_span_id`` are read-only) and returns it, edited, or ``None`` to drop it; a hook that raises drops the span. Tracing is off until this is provided. Spans export on a background timer even with ``sync_mode``; serverless handlers should call ``flush()`` before returning. Defaults to None.", + "isOptional": true, + "type": "dict" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import Posthog\n\nposthog = Posthog('', host='')" + } + ] + }, + { + "id": "alias", + "title": "alias", + "description": "Create an alias between two distinct IDs.", + "details": "", + "category": "Identification", + "params": [ + { + "name": "previous_id", + "description": "The previous distinct ID. Required - the call is dropped with a warning if it is missing or empty.", + "isOptional": true, + "type": "Number" + }, + { + "name": "distinct_id", + "description": "The new distinct ID to alias to. Falls back to the context distinct ID; the call is dropped with a warning if neither is available.", + "isOptional": true, + "type": "str" + }, + { + "name": "timestamp", + "description": "The timestamp of the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC.", + "isOptional": false, + "type": "datetime" + }, + { + "name": "uuid", + "description": "A unique identifier for the event. If provided, it must be a valid UUID string or uuid.UUID instance; invalid values are ignored and replaced with a newly generated UUID.", + "isOptional": true, + "type": "str" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this event.", + "isOptional": true, + "type": "bool" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.alias(previous_id='distinct_id', distinct_id='alias_id')" + } + ] + }, + { + "id": "capture", + "title": "capture", + "description": "Captures an event manually. [Learn about capture best practices](https://posthog.com/docs/product-analytics/capture-events)", + "details": "", + "category": "Capture", + "params": [ + { + "name": "event", + "description": "The event name to capture.", + "isOptional": true, + "type": "str" + }, + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalCaptureArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Anonymous event", + "code": "# Anonymous event\nposthog.capture('some-anon-event')" + }, + { + "id": "example_2", + "name": "Context usage", + "code": "# Context usage\nfrom posthog import identify_context, new_context\nwith new_context():\n identify_context('distinct_id_of_the_user')\n posthog.capture('user_signed_up')\n posthog.capture('user_logged_in')\n posthog.capture('some-custom-action', distinct_id='distinct_id_of_the_user')" + }, + { + "id": "example_3", + "name": "Set event properties", + "code": "# Set event properties\nposthog.capture(\n \"user_signed_up\",\n distinct_id=\"distinct_id_of_the_user\",\n properties={\n \"login_type\": \"email\",\n \"is_free_trial\": \"true\"\n }\n)" + }, + { + "id": "example_4", + "name": "Page view event", + "code": "# Page view event\nposthog.capture('$pageview', distinct_id=\"distinct_id_of_the_user\", properties={'$current_url': 'https://example.com'})" + } + ] + }, + { + "id": "capture_ai", + "title": "capture_ai", + "description": "Capture an AI event on the dedicated AI capture endpoint. Beta: the signature is stable; operational limits (per-event size cap, batching, endpoint) may change without notice. Takes the same arguments and returns the same value as `capture()`: the event UUID, or None when the event was not admitted (disabled client, or dropped by `before_send`). The event is queued on an isolated AI lane with its own consumer pool and a higher per-event size cap, posting to the dedicated AI ingestion endpoint. The payload is sent as given \u2014 no redaction or truncation is applied here.", + "details": "", + "category": "Capture", + "params": [ + { + "name": "event", + "description": "", + "isOptional": true, + "type": "str" + }, + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalCaptureArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + } + }, + { + "id": "capture_exception", + "title": "capture_exception", + "description": "Capture an exception for error tracking. When OpenTelemetry is installed and a valid span is active, its trace and span IDs are added as ``$trace_id`` and ``$span_id`` event properties.", + "details": "", + "category": "Error Tracking", + "params": [ + { + "name": "exception", + "description": "The exception to capture.", + "isOptional": true, + "type": "BaseException" + }, + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalCaptureArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "try:\n # Some code that might fail\n pass\nexcept Exception as e:\n posthog.capture_exception(e, 'user_distinct_id', properties=additional_properties)" + } + ] + }, + { + "id": "evaluate_flags", + "title": "evaluate_flags", + "description": "Evaluate all feature flags for a user in a single call and return a :class:`FeatureFlagEvaluations` snapshot. Branch on ``.is_enabled()`` / ``.get_flag()`` and pass the same snapshot to :meth:`capture` via the ``flags`` option so events carry the exact flag values the code branched on. Prefer this over repeated ``get_feature_flag()`` calls and over ``capture(send_feature_flags=True)`` \u2014 it consolidates flag evaluation into a single ``/flags`` request per incoming request. Local evaluation is transparent: when the poller resolves a flag, the snapshot's ``$feature_flag_called`` events are tagged ``locally_evaluated=True`` and reason ``\"Evaluated locally\"``.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The user's distinct ID. If ``None``, falls back to the context distinct_id. If still unresolvable, returns an empty snapshot.", + "isOptional": false, + "type": "Number" + }, + { + "name": "groups", + "description": "Mapping of group type to group key.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "Person properties to use for evaluation.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "Group properties keyed by group type.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "If True, never fall back to remote evaluation \u2014 flags that can't be evaluated locally are simply omitted from the snapshot.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup.", + "isOptional": true, + "type": "bool" + }, + { + "name": "flag_keys", + "description": "Optional list that scopes local evaluation, the underlying ``/flags`` request, and the returned snapshot. When omitted or ``None``, all flags are evaluated. An empty list returns an empty snapshot without evaluating flags. A requested key absent from loaded local definitions is included in one remote fallback per ``evaluate_flags`` call unless ``only_evaluate_locally`` is True. If the server also does not know the key, it is omitted from the snapshot.", + "isOptional": true, + "type": "list[str]" + }, + { + "name": "device_id", + "description": "Optional device ID override. If not provided, falls back to the context device_id (which may be set via tracing headers). Used by experience-continuity flags to match users across distinct_id changes.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "FeatureFlagEvaluations" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "flags = posthog.evaluate_flags(\n \"user_123\",\n person_properties={\"plan\": \"enterprise\"},\n)\nif flags.is_enabled(\"new-dashboard\"):\n render_new_dashboard()\nposthog.capture(\"page_viewed\", distinct_id=\"user_123\", flags=flags)" + } + ] + }, + { + "id": "feature_enabled", + "title": "feature_enabled", + "description": "Check if a feature flag is enabled for a user.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "The feature flag key.", + "isOptional": true, + "type": "str" + }, + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to only evaluate locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "send_feature_flag_events", + "description": "Whether to send feature flag events.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[bool]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "is_my_flag_enabled = posthog.feature_enabled('flag-key', 'distinct_id_of_your_user')\nif is_my_flag_enabled:\n # Do something differently for this user\n # Optional: fetch the payload\n matched_flag_payload = posthog.get_feature_flag_payload('flag-key', 'distinct_id_of_your_user')" + } + ] + }, + { + "id": "feature_flag_definitions", + "title": "feature_flag_definitions", + "description": "Return feature flag definitions loaded for local evaluation. Returns: The currently loaded feature flag definitions, or ``None`` before local evaluation has loaded definitions.", + "details": "", + "category": "Feature flags", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "flush", + "title": "flush", + "description": "Attempt immediate delivery of queued events and ended spans, keeping the client reusable. For a client shared across requests or serverless invocations (including the module-level client), call ``flush()`` before a short-lived invocation returns. Use ``shutdown()`` instead only for final process cleanup or when disposing of a client that will not be reused. Flush does not stop the background workers or feature flag poller, and does not end open spans. Returning does not guarantee server receipt: pending work can remain after a timeout or span export failure, and failed events may be dropped by the consumers. Calls made directly from SDK callbacks such as ``on_error`` are deferred rather than blocking the worker that invoked the callback.", + "details": "", + "category": null, + "params": [ + { + "name": "timeout_seconds", + "description": "Seconds to wait for event queues to drain, shared across event lanes. Defaults to 10 seconds. Pass ``None`` to wait without a time limit; this does not guarantee successful delivery. Queued spans flush concurrently. The span exporter waits up to this many seconds for an in-flight flush, then uses a fresh drain budget once it acquires the lock. If the lock wait times out, the in-flight flush is left to finish. At least one span request is attempted after acquiring the lock, even with no budget left; retriable failures are retried after backoff while budget remains, with one last attempt at the deadline. Each span request is bounded by the client's ``timeout`` in seconds, and the last request is not cut short. This is therefore not a strict wall-clock limit for the whole call. With ``None``, a retriable span failure is left for a later flush rather than retried within this call.", + "isOptional": true, + "type": "float" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.capture('event_name', distinct_id='user_id')\n# Before a serverless invocation returns; keep the shared client open.\nposthog.flush(timeout_seconds=10)" + } + ] + }, + { + "id": "get_active_span", + "title": "get_active_span", + "description": "The span that is active in the current context, or ``None``. Alpha. Only entering a span (``with posthog.start_span(...) as span:``) makes it active; a span started manually is not. Use it to propagate the trace to the next service: ``span.traceparent()`` is the header value.", + "details": "", + "category": "Tracing", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[Span]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "span = posthog.get_active_span()\nif span is not None:\n headers[\"traceparent\"] = span.traceparent()" + } + ] + }, + { + "id": "get_all_flags", + "title": "get_all_flags", + "description": "Get all feature flags for a user.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to only evaluate locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "flag_keys_to_evaluate", + "description": "A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance.", + "isOptional": true, + "type": "list[str]" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[dict[str, Union[bool, str]]]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.get_all_flags('distinct_id_of_your_user')" + } + ] + }, + { + "id": "get_all_flags_and_payloads", + "title": "get_all_flags_and_payloads", + "description": "Get all feature flags and their payloads for a user.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to only evaluate locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "flag_keys_to_evaluate", + "description": "A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance.", + "isOptional": true, + "type": "list[str]" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "FlagsAndPayloads" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.get_all_flags_and_payloads('distinct_id_of_your_user')" + } + ] + }, + { + "id": "get_feature_flag", + "title": "get_feature_flag", + "description": "Get multivariate feature flag value for a user.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "The feature flag key.", + "isOptional": true, + "type": "str" + }, + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to only evaluate locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "send_feature_flag_events", + "description": "Whether to send feature flag events.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Union[bool, str, any]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "enabled_variant = posthog.get_feature_flag('flag-key', 'distinct_id_of_your_user')\nif enabled_variant == 'variant-key': # replace 'variant-key' with the key of your variant\n # Do something differently for this user\n # Optional: fetch the payload\n matched_flag_payload = posthog.get_feature_flag_payload('flag-key', 'distinct_id_of_your_user')" + } + ] + }, + { + "id": "get_feature_flag_evaluation_runtime", + "title": "get_feature_flag_evaluation_runtime", + "description": "Return where a locally loaded feature flag is meant to be evaluated.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "The feature flag key.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[FeatureFlagEvaluationRuntime]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import FeatureFlagEvaluationRuntime\n\nruntime = posthog.get_feature_flag_evaluation_runtime(\"my-flag\")\nif runtime is FeatureFlagEvaluationRuntime.SERVER:\n ..." + } + ] + }, + { + "id": "get_feature_flag_keys_by_evaluation_runtime", + "title": "get_feature_flag_keys_by_evaluation_runtime", + "description": "Return the keys of locally loaded flags that a runtime can evaluate. A flag set to ``FeatureFlagEvaluationRuntime.ALL`` suits either runtime, so it is returned for ``CLIENT`` and for ``SERVER``, and asking for ``ALL`` returns every loaded flag. Use this to decide which flags to hand to a browser when a backend serves flags to its own frontend.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "evaluation_runtime", + "description": "The runtime to match, as a ``FeatureFlagEvaluationRuntime`` or its string value.", + "isOptional": true, + "type": "FeatureFlagEvaluationRuntime" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "list[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import FeatureFlagEvaluationRuntime\n\nclient_keys = posthog.get_feature_flag_keys_by_evaluation_runtime(\n FeatureFlagEvaluationRuntime.CLIENT\n)" + } + ] + }, + { + "id": "get_feature_flag_payload", + "title": "get_feature_flag_payload", + "description": "Get the payload for a feature flag.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "The feature flag key.", + "isOptional": true, + "type": "str" + }, + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "match_value", + "description": "The specific flag value to get payload for.", + "isOptional": false, + "type": "bool" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to only evaluate locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "send_feature_flag_events", + "description": "Deprecated. Use get_feature_flag() instead if you need events.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[object]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "is_my_flag_enabled = posthog.feature_enabled('flag-key', 'distinct_id_of_your_user')\n\nif is_my_flag_enabled:\n # Do something differently for this user\n # Optional: fetch the payload\n matched_flag_payload = posthog.get_feature_flag_payload('flag-key', 'distinct_id_of_your_user')" + } + ] + }, + { + "id": "get_feature_flag_result", + "title": "get_feature_flag_result", + "description": "Get a FeatureFlagResult object which contains the flag result and payload for a key by evaluating locally or remotely depending on whether local evaluation is enabled and the flag can be locally evaluated. This also captures the `$feature_flag_called` event unless `send_feature_flag_events` is `False`.", + "details": "", + "category": null, + "params": [ + { + "name": "key", + "description": "The feature flag key.", + "isOptional": true, + "type": "str" + }, + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to only evaluate locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "send_feature_flag_events", + "description": "Whether to send feature flag events.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[FeatureFlagResult]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "flag_result = posthog.get_feature_flag_result('flag-key', 'distinct_id_of_your_user')\nif flag_result and flag_result.get_value() == 'variant-key':\n # Do something differently for this user\n # Optional: fetch the payload\n matched_flag_payload = flag_result.payload" + } + ] + }, + { + "id": "get_feature_flags_and_payloads", + "title": "get_feature_flags_and_payloads", + "description": "Get feature flags and payloads for a user.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "flag_keys_to_evaluate", + "description": "A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance.", + "isOptional": true, + "type": "list[str]" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "FlagsAndPayloads" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "result = posthog.get_feature_flags_and_payloads('')" + } + ] + }, + { + "id": "get_feature_payloads", + "title": "get_feature_payloads", + "description": "Get feature flag payloads for a user, preserving valid serialized JSON.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "flag_keys_to_evaluate", + "description": "A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance.", + "isOptional": true, + "type": "list[str]" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "dict[str, Optional[str]]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "payloads = posthog.get_feature_payloads('')" + } + ] + }, + { + "id": "get_feature_variants", + "title": "get_feature_variants", + "description": "Get feature flag variants for a user.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "flag_keys_to_evaluate", + "description": "A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance.", + "isOptional": true, + "type": "list[str]" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "dict[str, Union[bool, str]]" + } + }, + { + "id": "get_flags_decision", + "title": "get_flags_decision", + "description": "Get feature flags decision.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": false, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "flag_keys_to_evaluate", + "description": "A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance.", + "isOptional": true, + "type": "list[str]" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "FlagsResponse" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "decision = posthog.get_flags_decision('user123')" + } + ] + }, + { + "id": "get_remote_config_payload", + "title": "get_remote_config_payload", + "description": "Get the payload for a remote config feature flag.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "The remote config feature flag key.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "get_tags", + "title": "get_tags", + "description": "Get all tags from the current context. Returns: Dict of all tags in the current context.", + "details": "", + "category": "Contexts", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "dict[str, Any]" + } + }, + { + "id": "group_identify", + "title": "group_identify", + "description": "Identify a group and set its properties.", + "details": "", + "category": "Identification", + "params": [ + { + "name": "group_type", + "description": "The type of group (e.g., 'company', 'team'). Required - the call is dropped with a warning if it is missing or empty.", + "isOptional": true, + "type": "str" + }, + { + "name": "group_key", + "description": "The unique identifier for the group. Required - the call is dropped with a warning if it is missing or empty.", + "isOptional": true, + "type": "str" + }, + { + "name": "properties", + "description": "A dictionary of properties to set on the group.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "timestamp", + "description": "The timestamp of the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC.", + "isOptional": false, + "type": "datetime" + }, + { + "name": "uuid", + "description": "A unique identifier for the event. If provided, it must be a valid UUID string or uuid.UUID instance; invalid values are ignored and replaced with a newly generated UUID.", + "isOptional": false, + "type": "str" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this event.", + "isOptional": true, + "type": "bool" + }, + { + "name": "distinct_id", + "description": "The distinct ID of the user performing the action.", + "isOptional": false, + "type": "Number" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.group_identify('company', 'company_id_in_your_db', {\n 'name': 'Awesome Inc.',\n 'employees': 11\n})" + } + ] + }, + { + "id": "identify_context", + "title": "identify_context", + "description": "Identify the current context with a distinct ID.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "distinct_id", + "description": "The distinct ID to associate with the current context and its children.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + } + }, + { + "id": "join", + "title": "join", + "description": "Attempt to process queued events and end the consumer threads. Do not use directly, call `shutdown()` instead. Failed or undrainable events may be dropped and reported through logging or ``on_error``; returning does not guarantee server receipt. Lifecycle cleanup is attempted once, and cleanup failures are logged without retry.", + "details": "", + "category": null, + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.join()" + } + ] + }, + { + "id": "load_feature_flags", + "title": "load_feature_flags", + "description": "Load feature flags for local evaluation.", + "details": "", + "category": "Feature flags", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.load_feature_flags()" + } + ] + }, + { + "id": "new_context", + "title": "new_context", + "description": "Create a new context for managing shared state. Learn more about [contexts](/docs/libraries/python#contexts).", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "fresh", + "description": "Whether to create a fresh context that doesn't inherit from parent.", + "isOptional": false, + "type": "bool" + }, + { + "name": "capture_exceptions", + "description": "Whether to automatically capture exceptions in this context. If omitted, defaults to this client's exception autocapture setting.", + "isOptional": true, + "type": "bool" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "with client.new_context():\n client.identify_context('')\n client.capture('event_name')" + } + ] + }, + { + "id": "scoped", + "title": "scoped", + "description": "Decorator that creates a new context for the wrapped function using this client.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "fresh", + "description": "Whether to create a fresh context that doesn't inherit from parent.", + "isOptional": false, + "type": "bool" + }, + { + "name": "capture_exceptions", + "description": "Whether to automatically capture exceptions in this context. If omitted, defaults to this client's exception autocapture setting.", + "isOptional": true, + "type": "bool" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "set", + "title": "set", + "description": "Set properties on a person profile.", + "details": "", + "category": "Identification", + "params": [ + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalSetArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Set with distinct id", + "code": "# Set with distinct id\nposthog.set(distinct_id='user123', properties={'name': 'Max Hedgehog'})" + } + ] + }, + { + "id": "set_context_device_id", + "title": "set_context_device_id", + "description": "Set the device ID for the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "device_id", + "description": "The device ID to associate with the current context and its children.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + } + }, + { + "id": "set_context_session", + "title": "set_context_session", + "description": "Set the session ID for the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "session_id", + "description": "The session ID to associate with the current context and its children.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + } + }, + { + "id": "set_once", + "title": "set_once", + "description": "Set properties on a person profile only if they haven't been set before.", + "details": "", + "category": "Identification", + "params": [ + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalSetArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.set_once(distinct_id='user123', properties={'initial_signup_date': '2024-01-01'})" + } + ] + }, + { + "id": "shutdown", + "title": "shutdown", + "description": "Attempt final delivery and permanently shut down the client. Call this for final process cleanup or when disposing of a client that will not be reused. It closes capture lanes, stops background workers and the feature flag poller, and tears down integrations. Do not call it at the end of each request or serverless invocation when the client is shared; call ``flush()`` before returning instead. A shut-down client cannot be reused; create a new client if needed. Normally this method blocks until queued events have been attempted and cleanup finishes. Failed or undrainable events may be dropped and reported through logging or ``on_error``; returning does not guarantee server receipt. Event draining has no timeout. Queued spans get one final flush with up to 30 seconds waiting for an in-flight flush, then a fresh 30-second drain budget; the last HTTP request is not cut short. Any spans it cannot send are discarded with a warning, as are spans still open. Lifecycle cleanup is attempted once, and cleanup failures are logged without retry. When called directly from an SDK callback such as ``on_error``, shutdown is deferred to avoid blocking the worker that invoked the callback. If the callback must coordinate a blocking shutdown, have it signal an application-owned thread and return before that thread calls shutdown. Do not wait inside the callback for another thread or task that calls a lifecycle method.", + "details": "", + "category": null, + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.shutdown()" + } + ] + }, + { + "id": "start_span", + "title": "start_span", + "description": "Start a span for distributed tracing. Alpha. Returns a span handle. Use it as a context manager to make it the active span for the block and end it on exit (recording a raised exception on the way out); or call ``end()`` yourself for a span that cannot wrap a block. Spans started inside the block nest under it automatically. Always returns a usable handle, even when tracing is off, so calling code never branches.", + "details": "", + "category": "Tracing", + "params": [ + { + "name": "name", + "description": "A low-cardinality operation name, e.g. ``GET /users/:id``. Variable values belong in attributes, not the name.", + "isOptional": true, + "type": "str" + }, + { + "name": "kind", + "description": "``internal`` (default), ``server``, ``client``, ``producer`` or ``consumer``.", + "isOptional": true, + "type": "str" + }, + { + "name": "attributes", + "description": "Initial attributes.", + "isOptional": true, + "type": "Mapping[str, Any]" + }, + { + "name": "parent", + "description": "A span handle, or an inbound W3C ``traceparent`` header value to continue a remote trace. Defaults to the active span. A forked child starts with no active span; pass the parent span to continue a trace across a fork.", + "isOptional": false, + "type": "Span" + }, + { + "name": "tracestate", + "description": "The inbound ``tracestate`` header accompanying a ``traceparent`` string ``parent``; preserved and propagated.", + "isOptional": true, + "type": "str" + }, + { + "name": "start_time", + "description": "A ``datetime`` or epoch seconds, to backdate the span.", + "isOptional": false, + "type": "datetime" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Span" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog = Posthog(\"\", traces={\"service_name\": \"checkout-api\"})\n\nwith posthog.start_span(\"POST /checkout\", parent=request.headers.get(\"traceparent\")) as span:\n span.set_attribute(\"plan\", user.plan)\n with posthog.start_span(\"db.query\", kind=\"client\"):\n ...\n outgoing_headers = {\"traceparent\": span.traceparent()}" + } + ] + }, + { + "id": "tag", + "title": "tag", + "description": "Add a tag to the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "name", + "description": "The tag key.", + "isOptional": true, + "type": "str" + }, + { + "name": "value", + "description": "The tag value.", + "isOptional": true, + "type": "Any" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + } + } + ] + }, + { + "id": "PostHogModule", + "title": "PostHog Module Functions", + "description": "Global functions available in the PostHog module", + "functions": [ + { + "id": "alias", + "title": "alias", + "description": "Associate user behaviour before and after they e.g. register, login, or perform some other identifying action.", + "details": "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?\". Particularly useful for associating user behaviour before and after they e.g. register, login, or perform some other identifying action.", + "category": "Identification", + "params": [ + { + "name": "previous_id", + "description": "The unique ID of the user before", + "isOptional": true, + "type": "Number" + }, + { + "name": "distinct_id", + "description": "The current unique id", + "isOptional": true, + "type": "str" + }, + { + "name": "timestamp", + "description": "Optional timestamp for the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC.", + "isOptional": false, + "type": "datetime" + }, + { + "name": "uuid", + "description": "Optional UUID for the event", + "isOptional": true, + "type": "str" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup", + "isOptional": true, + "type": "bool" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Alias user", + "code": "# Alias user\nfrom posthog import alias\nalias(previous_id='distinct_id', distinct_id='alias_id')" + } + ] + }, + { + "id": "capture", + "title": "capture", + "description": "Capture anything a user does within your system.", + "details": "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. A capture call requires an event name to specify the event. We recommend using [verb] [noun], like `movie played` or `movie updated` to easily identify what your events mean later on. Capture takes a number of optional arguments, which are defined by the `OptionalCaptureArgs` type.", + "category": "Events", + "params": [ + { + "name": "event", + "description": "The event name to specify the event **kwargs: Optional arguments including:", + "isOptional": true, + "type": "str" + }, + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalCaptureArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Context and capture usage", + "code": "# Context and capture usage\nfrom posthog import new_context, identify_context, tag_context, capture\n# Enter a new context (e.g. a request/response cycle, an instance of a background job, etc)\nwith new_context():\n # Associate this context with some user, by distinct_id\n identify_context('some user')\n\n # Capture an event, associated with the context-level distinct ID ('some user')\n capture('movie started')\n\n # Capture an event associated with some other user (overriding the context-level distinct ID)\n capture('movie joined', distinct_id='some-other-user')\n\n # Capture an event with some properties\n capture('movie played', properties={'movie_id': '123', 'category': 'romcom'})\n\n # Capture an event with some properties\n capture('purchase', properties={'product_id': '123', 'category': 'romcom'})\n # Capture an event with some associated group\n capture('purchase', groups={'company': 'id:5'})\n\n # Adding a tag to the current context will cause it to appear on all subsequent events\n tag_context('some-tag', 'some-value')\n\n capture('another-event') # Will be captured with `'some-tag': 'some-value'` in the properties dict" + }, + { + "id": "example_2", + "name": "Set event properties", + "code": "# Set event properties\nfrom posthog import capture\ncapture(\n \"user_signed_up\",\n distinct_id=\"distinct_id_of_the_user\",\n properties={\n \"login_type\": \"email\",\n \"is_free_trial\": \"true\"\n }\n)" + } + ] + }, + { + "id": "capture_ai", + "title": "capture_ai", + "description": "Capture an AI event on the dedicated AI capture endpoint. Beta: the signature is stable; operational limits (per-event size cap, batching, endpoint) may change without notice. Takes the same arguments and returns the same value as `capture()`: the event UUID, or None when the event was not admitted (disabled client, or dropped by `before_send`). The event is delivered on an isolated queue with its own consumer pool and a higher per-event size cap, posting to the dedicated AI ingestion endpoint. The payload is sent as given \u2014 no redaction or truncation is applied here.", + "details": "", + "category": "Events", + "params": [ + { + "name": "event", + "description": "The event name, normally one of the `$ai_*` event names. **kwargs: Same optional arguments as `capture()`.", + "isOptional": true, + "type": "str" + }, + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalCaptureArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import capture_ai\n\nuuid = capture_ai(\n \"$ai_generation\",\n distinct_id=\"user_123\",\n properties={\"$ai_model\": \"gpt-5\"},\n)" + } + ] + }, + { + "id": "capture_exception", + "title": "capture_exception", + "description": "Capture exceptions that happen in your code.", + "details": "Capture exception is idempotent - if it is called twice with the same exception instance, only a occurrence will be tracked in posthog. This is because, generally, contexts will cause exceptions to be captured automatically. However, to ensure you track an exception, if you catch and do not re-raise it, capturing it manually is recommended, unless you are certain it will have crossed a context boundary (e.g. by existing a `with posthog.new_context():` block already). If the passed exception was raised and caught, the captured stack trace will consist of every frame between where the exception was raised and the point at which it is captured (the \"traceback\"). If the passed exception was never raised, e.g. if you call `posthog.capture_exception(ValueError(\"Some Error\"))`, the stack trace captured will be the full stack trace at the moment the exception was captured. Note that heavy use of contexts will lead to truncated stack traces, as the exception will be captured by the context entered most recently, 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. `capture_exception` takes the same set of optional arguments as `capture`.", + "category": "Events", + "params": [ + { + "name": "exception", + "description": "The exception to capture. If not provided, the current exception is captured via `sys.exc_info()` **kwargs: Optional capture arguments including distinct_id, properties, timestamp, uuid, groups, flags, send_feature_flags, and disable_geoip. Overriding reserved exception properties through ``properties`` is deprecated and will stop working in the next major version.", + "isOptional": false, + "type": "BaseException" + }, + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalCaptureArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Capture exception", + "code": "# Capture exception\nfrom posthog import capture_exception\ntry:\n risky_operation()\nexcept Exception as e:\n capture_exception(e)" + } + ] + }, + { + "id": "evaluate_flags", + "title": "evaluate_flags", + "description": "Evaluate all feature flags for a user in a single call and return a :class:`FeatureFlagEvaluations` snapshot. Branch on ``.is_enabled()`` / ``.get_flag()`` and pass the same snapshot to ``capture()`` via the ``flags`` option so events carry the exact flag values the code branched on. Prefer this over repeated ``get_feature_flag()`` calls and over ``capture(send_feature_flags=True)`` \u2014 it consolidates flag evaluation into a single ``/flags`` request per incoming request.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The user's distinct ID. If ``None``, falls back to the context distinct_id. If still unresolvable, returns an empty snapshot.", + "isOptional": false, + "type": "Number" + }, + { + "name": "groups", + "description": "Mapping of group type to group key.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "Person properties to use for evaluation.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "Group properties keyed by group type.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "If ``True``, never fall back to remote evaluation and omit flags that cannot be evaluated locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup.", + "isOptional": true, + "type": "bool" + }, + { + "name": "flag_keys", + "description": "Optional list that scopes local evaluation, the underlying ``/flags`` request, and the returned snapshot. When omitted or ``None``, all flags are evaluated. An empty list returns an empty snapshot without evaluating flags. A requested key absent from loaded local definitions is included in one remote fallback per ``evaluate_flags`` call unless ``only_evaluate_locally`` is ``True``. If the server also does not know the key, it is omitted from the snapshot.", + "isOptional": true, + "type": "list[str]" + }, + { + "name": "device_id", + "description": "Optional device ID override. If not provided, falls back to the context device_id (which may be set via tracing headers). Used by experience-continuity flags to match users across distinct_id changes.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "FeatureFlagEvaluations" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import evaluate_flags, capture\nflags = evaluate_flags(\"user_123\", person_properties={\"plan\": \"enterprise\"})\nif flags.is_enabled(\"new-dashboard\"):\n render_new_dashboard()\ncapture(\"page_viewed\", distinct_id=\"user_123\", flags=flags)" + } + ] + }, + { + "id": "feature_enabled", + "title": "feature_enabled", + "description": "Use feature flags to enable or disable features for users.", + "details": "You can call `posthog.load_feature_flags()` before to make sure you're not doing unexpected requests.", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "The feature flag key", + "isOptional": true, + "type": "str" + }, + { + "name": "distinct_id", + "description": "The user's distinct ID", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "Groups mapping", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "Person properties", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "Group properties", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to evaluate only locally", + "isOptional": false, + "type": "bool" + }, + { + "name": "send_feature_flag_events", + "description": "Whether to send feature flag events", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "Optional device ID override for experience-continuity flags", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[bool]" + }, + "examples": [ + { + "id": "example_1", + "name": "Boolean feature flag", + "code": "# Boolean feature flag\nfrom posthog import feature_enabled, get_feature_flag_payload\nis_my_flag_enabled = feature_enabled('flag-key', 'distinct_id_of_your_user')\nif is_my_flag_enabled:\n matched_flag_payload = get_feature_flag_payload('flag-key', 'distinct_id_of_your_user')" + } + ] + }, + { + "id": "feature_flag_definitions", + "title": "feature_flag_definitions", + "description": "Returns loaded feature flags.", + "details": "Returns loaded feature flags, if any. Helpful for debugging what flag information you have loaded.", + "category": "Feature flags", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import feature_flag_definitions\ndefinitions = feature_flag_definitions()" + } + ] + }, + { + "id": "flush", + "title": "flush", + "description": "Attempt immediate delivery of queued events and ended spans, keeping the global client reusable. Call ``flush()`` before a short-lived request or serverless invocation returns when using the shared module-level client. Use ``shutdown()`` only for final process cleanup or a client that will not be reused. Flush does not stop the background workers or feature flag poller, and does not end open spans. Returning does not guarantee server receipt: pending work can remain after a timeout or span export failure, and failed events may be dropped by consumers. Calls made directly from SDK callbacks such as ``on_error`` are deferred rather than blocking the worker that invoked the callback.", + "details": "", + "category": "Client management", + "params": [ + { + "name": "timeout_seconds", + "description": "Seconds to wait for event queues to drain, shared across event lanes. Defaults to 10 seconds. Pass ``None`` to wait without a time limit; this does not guarantee successful delivery. Queued spans flush concurrently, waiting up to this many seconds for an in-flight flush before starting a fresh drain budget. A lock wait timeout leaves the in-flight flush to finish. At least one span request is attempted after acquiring the lock, even with no budget left; retriable failures are retried after backoff while budget remains, with one last attempt at the deadline. The last span request is not cut short and is bounded by the client's ``timeout`` in seconds, so this is not a strict wall-clock limit for the whole call. With ``None``, a retriable span failure is left for a later flush rather than retried within this call.", + "isOptional": true, + "type": "float" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "import posthog\nposthog.capture('event_name', distinct_id='user_id')\n# Before a serverless invocation returns; keep the global client open.\nposthog.flush(timeout_seconds=10)" + } + ] + }, + { + "id": "get_active_span", + "title": "get_active_span", + "description": "The span that is active in the current context, or ``None``. Alpha. Only entering a span (``with posthog.start_span(...) as span:``) makes it active; a span started manually is not. Use it to propagate the trace to the next service: ``span.traceparent()`` is the header value.", + "details": "", + "category": "Tracing", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[Span]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "span = posthog.get_active_span()\nif span is not None:\n headers[\"traceparent\"] = span.traceparent()" + } + ] + }, + { + "id": "get_all_flags", + "title": "get_all_flags", + "description": "Get all flags for a given user.", + "details": "Flags are key-value pairs where the key is the flag key and the value is the flag variant, or True, or False.", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The user's distinct ID", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "Groups mapping", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "Person properties", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "Group properties", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to evaluate only locally", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "Optional device ID override for experience-continuity flags", + "isOptional": true, + "type": "str" + }, + { + "name": "flag_keys_to_evaluate", + "description": "Optional list of flag keys to evaluate (evaluates all if None)", + "isOptional": true, + "type": "list[str]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[dict[str, Union[bool, str]]]" + }, + "examples": [ + { + "id": "example_1", + "name": "All flags for user", + "code": "# All flags for user\nfrom posthog import get_all_flags\nget_all_flags('distinct_id_of_your_user')" + } + ] + }, + { + "id": "get_all_flags_and_payloads", + "title": "get_all_flags_and_payloads", + "description": "Get all feature flag values and payloads for a user.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The user's distinct ID.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "Mapping of group type to group key.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "Person properties to use for evaluation.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "Group properties keyed by group type.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to evaluate only locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup.", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "Optional device ID override for experience-continuity flags.", + "isOptional": true, + "type": "str" + }, + { + "name": "flag_keys_to_evaluate", + "description": "Optional list of flag keys to evaluate. Evaluates all flags when omitted.", + "isOptional": true, + "type": "list[str]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "FlagsAndPayloads" + } + }, + { + "id": "get_feature_flag", + "title": "get_feature_flag", + "description": "Get feature flag variant for users. Used with experiments.", + "details": "`groups` are a mapping from group type to group key. So, if you have a group type of \"organization\" and a group key of \"5\", you would pass groups={\"organization\": \"5\"}. `group_properties` take the format: { group_type_name: { group_properties } }. So, for example, if you have the group type \"organization\" and the group key \"5\", with the properties name, and employee count, you'll send these as: group_properties={\"organization\": {\"name\": \"PostHog\", \"employees\": 11}}.", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "The feature flag key", + "isOptional": true, + "type": "str" + }, + { + "name": "distinct_id", + "description": "The user's distinct ID", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "Groups mapping from group type to group key", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "Person properties", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "Group properties in format { group_type_name: { group_properties } }", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to evaluate only locally", + "isOptional": false, + "type": "bool" + }, + { + "name": "send_feature_flag_events", + "description": "Whether to send feature flag events", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "Optional device ID override for experience-continuity flags", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Union[bool, str, any]" + }, + "examples": [ + { + "id": "example_1", + "name": "Multivariate feature flag", + "code": "# Multivariate feature flag\nfrom posthog import get_feature_flag, get_feature_flag_payload\nenabled_variant = get_feature_flag('flag-key', 'distinct_id_of_your_user')\nif enabled_variant == 'variant-key':\n matched_flag_payload = get_feature_flag_payload('flag-key', 'distinct_id_of_your_user')" + } + ] + }, + { + "id": "get_feature_flag_evaluation_runtime", + "title": "get_feature_flag_evaluation_runtime", + "description": "Return where a locally loaded feature flag is meant to be evaluated.", + "details": "Reads the `evaluation_runtime` each flag definition carries, so no extra request is made. Returns `None` when local evaluation has not loaded a definition for this key. A definition that carries no runtime reports `FeatureFlagEvaluationRuntime.ALL`, the default PostHog applies.", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[FeatureFlagEvaluationRuntime]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import FeatureFlagEvaluationRuntime, get_feature_flag_evaluation_runtime\nruntime = get_feature_flag_evaluation_runtime(\"my-flag\")" + } + ] + }, + { + "id": "get_feature_flag_keys_by_evaluation_runtime", + "title": "get_feature_flag_keys_by_evaluation_runtime", + "description": "Return the keys of locally loaded flags that a runtime can evaluate.", + "details": "A flag set to `FeatureFlagEvaluationRuntime.ALL` suits either runtime, so it is returned for `CLIENT` and for `SERVER`. Use this to decide which flags to hand to a browser when a backend serves flags to its own frontend.", + "category": "Feature flags", + "params": [ + { + "name": "evaluation_runtime", + "description": "", + "isOptional": true, + "type": "FeatureFlagEvaluationRuntime" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "list[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import FeatureFlagEvaluationRuntime, get_feature_flag_keys_by_evaluation_runtime\nclient_keys = get_feature_flag_keys_by_evaluation_runtime(FeatureFlagEvaluationRuntime.CLIENT)" + } + ] + }, + { + "id": "get_feature_flag_payload", + "title": "get_feature_flag_payload", + "description": "Get the payload associated with a feature flag value. Deprecated for new code. Prefer ``evaluate_flags()`` and ``flags.get_flag_payload(key)`` so flag evaluation happens once per request.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "The feature flag key.", + "isOptional": true, + "type": "str" + }, + { + "name": "distinct_id", + "description": "The user's distinct ID.", + "isOptional": true, + "type": "Number" + }, + { + "name": "match_value", + "description": "Optional flag value to use when selecting a payload.", + "isOptional": false, + "type": "bool" + }, + { + "name": "groups", + "description": "Mapping of group type to group key.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "Person properties to use for evaluation.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "Group properties keyed by group type.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to evaluate only locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "send_feature_flag_events", + "description": "Whether to send a $feature_flag_called event.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup.", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "Optional device ID override for experience-continuity flags.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[object]" + } + }, + { + "id": "get_feature_flag_result", + "title": "get_feature_flag_result", + "description": "Get a FeatureFlagResult object which contains the flag result and payload. This method evaluates a feature flag and returns a FeatureFlagResult object containing: - enabled: Whether the flag is enabled - variant: The variant value if the flag has variants - payload: The payload associated with the flag (automatically deserialized from JSON) - key: The flag key - reason: Why the flag was enabled/disabled", + "details": "", + "category": null, + "params": [ + { + "name": "key", + "description": "The feature flag key.", + "isOptional": true, + "type": "str" + }, + { + "name": "distinct_id", + "description": "The user's distinct ID.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "Mapping of group type to group key.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "Person properties to use for evaluation.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "Group properties keyed by group type.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to evaluate only locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "send_feature_flag_events", + "description": "Whether to send a $feature_flag_called event.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup.", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "Optional device ID override for experience-continuity flags.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[FeatureFlagResult]" + } + }, + { + "id": "get_remote_config_payload", + "title": "get_remote_config_payload", + "description": "Get the payload for a remote config feature flag.", + "details": "", + "category": null, + "params": [ + { + "name": "key", + "description": "The key of the feature flag", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "get_tags", + "title": "get_tags", + "description": "Get all tags from the current context. Returns: Dict of all tags in the current context", + "details": "", + "category": "Contexts", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "dict[str, Any]" + } + }, + { + "id": "group_identify", + "title": "group_identify", + "description": "Set properties on a group.", + "details": "", + "category": "Identification", + "params": [ + { + "name": "group_type", + "description": "Type of your group. Required - the call is dropped with a warning if it is missing or empty.", + "isOptional": true, + "type": "str" + }, + { + "name": "group_key", + "description": "Unique identifier of the group. Required - the call is dropped with a warning if it is missing or empty.", + "isOptional": true, + "type": "str" + }, + { + "name": "properties", + "description": "Properties to set on the group", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "timestamp", + "description": "Optional timestamp for the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC.", + "isOptional": false, + "type": "datetime" + }, + { + "name": "uuid", + "description": "Optional UUID for the event", + "isOptional": true, + "type": "str" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup", + "isOptional": true, + "type": "bool" + }, + { + "name": "distinct_id", + "description": "Optional distinct ID of the user performing the action", + "isOptional": false, + "type": "Number" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Group identify", + "code": "# Group identify\nfrom posthog import group_identify\ngroup_identify('company', 'company_id_in_your_db', {\n 'name': 'Awesome Inc.',\n 'employees': 11\n})" + } + ] + }, + { + "id": "identify_context", + "title": "identify_context", + "description": "Identify the current context with a distinct ID.", + "details": "", + "category": "Identification", + "params": [ + { + "name": "distinct_id", + "description": "The distinct ID to associate with the current context and its children", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import identify_context\nidentify_context(\"user_123\")" + } + ] + }, + { + "id": "join", + "title": "join", + "description": "Attempt to process queued events and stop the client's background workers. Use `shutdown()` directly in most cases. Failed or undrainable events may be dropped and reported through logging or ``on_error``; returning does not guarantee server receipt. Lifecycle cleanup is attempted once, and cleanup failures are logged without retry.", + "details": "", + "category": "Client management", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import join\njoin()" + } + ] + }, + { + "id": "load_feature_flags", + "title": "load_feature_flags", + "description": "Load feature flag definitions from PostHog.", + "details": "", + "category": "Feature flags", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import load_feature_flags\nload_feature_flags()" + } + ] + }, + { + "id": "new_context", + "title": "new_context", + "description": "Create a new context scope that will be active for the duration of the with block.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "fresh", + "description": "Whether to start with a fresh context (default: False)", + "isOptional": false, + "type": "bool" + }, + { + "name": "capture_exceptions", + "description": "Whether to capture exceptions raised within the context. If omitted, defaults to the relevant client's exception autocapture setting.", + "isOptional": true, + "type": "bool" + }, + { + "name": "client", + "description": "Optional Posthog client instance to use for this context (default: None)", + "isOptional": true, + "type": "Client" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import new_context, tag, capture\nwith new_context():\n tag(\"request_id\", \"123\")\n capture(\"event_name\", properties={\"property\": \"value\"})" + } + ] + }, + { + "id": "scoped", + "title": "scoped", + "description": "Decorator that creates a new context for the function.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "fresh", + "description": "Whether to start with a fresh context (default: False)", + "isOptional": false, + "type": "bool" + }, + { + "name": "capture_exceptions", + "description": "Whether to capture and track exceptions with posthog error tracking. If omitted, defaults to the global exception autocapture setting.", + "isOptional": true, + "type": "bool" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import scoped, tag, capture\n@scoped()\ndef process_payment(payment_id):\n tag(\"payment_id\", payment_id)\n capture(\"payment_started\")" + } + ] + }, + { + "id": "set", + "title": "set", + "description": "Set properties on a user record.", + "details": "This will overwrite previous people property values. Generally operates similar to `capture`, with distinct_id being an optional argument, defaulting to the current context's distinct ID. If there is no context-level distinct ID, and no override distinct_id is passed, this function will do nothing. Context tags are folded into $set properties, so tagging the current context and then calling `set` will cause those tags to be set on the user (unlike capture, which causes them to just be set on the event).", + "category": "Identification", + "params": [ + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalSetArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Set person properties", + "code": "# Set person properties\nfrom posthog import set\nset(distinct_id='distinct_id', properties={'name': 'Max Hedgehog'})" + } + ] + }, + { + "id": "set_capture_exception_code_variables_context", + "title": "set_capture_exception_code_variables_context", + "description": "Override code-variable capture for exceptions in the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "enabled", + "description": "Whether exceptions captured in this context should include local variable values from stack frames.", + "isOptional": true, + "type": "bool" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "set_code_variables_detect_secrets_context", + "title": "set_code_variables_detect_secrets_context", + "description": "Whether to apply entropy-based secret detection as a last-resort redaction of high-entropy values (API keys, tokens, strong passwords) in captured code variables for the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "enabled", + "description": "", + "isOptional": true, + "type": "bool" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "set_code_variables_ignore_patterns_context", + "title": "set_code_variables_ignore_patterns_context", + "description": "Override code-variable ignore patterns for exceptions in the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "ignore_patterns", + "description": "Variable-name patterns that should be omitted entirely when code variables are captured.", + "isOptional": true, + "type": "list[str]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "set_code_variables_mask_patterns_context", + "title": "set_code_variables_mask_patterns_context", + "description": "Override code-variable mask patterns for exceptions in the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "mask_patterns", + "description": "Variable-name patterns whose values should be replaced with ``***`` when code variables are captured.", + "isOptional": true, + "type": "list[str]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "set_code_variables_mask_url_credentials_context", + "title": "set_code_variables_mask_url_credentials_context", + "description": "Whether to scrub credentials embedded in URLs/DSNs (e.g. user:pass@host) from captured code variables for the current context.", + "details": "", + "category": null, + "params": [ + { + "name": "enabled", + "description": "", + "isOptional": true, + "type": "bool" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "set_context_device_id", + "title": "set_context_device_id", + "description": "Set the device ID for the current context, associating all feature flag requests in this or child contexts with the given device ID.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "device_id", + "description": "The device ID to associate with the current context and its children", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import set_context_device_id\nset_context_device_id(\"device_123\")" + } + ] + }, + { + "id": "set_context_session", + "title": "set_context_session", + "description": "Set the session ID for the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "session_id", + "description": "The session ID to associate with the current context and its children", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import set_context_session\nset_context_session(\"session_123\")" + } + ] + }, + { + "id": "set_once", + "title": "set_once", + "description": "Set properties on a user record, only if they do not yet exist.", + "details": "This will not overwrite previous people property values, unlike `set`. Otherwise, operates in an identical manner to `set`.", + "category": "Identification", + "params": [ + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalSetArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Set property once", + "code": "# Set property once\nfrom posthog import set_once\nset_once(distinct_id='distinct_id', properties={'initial_url': '/blog'})" + } + ] + }, + { + "id": "setup", + "title": "setup", + "description": "Create or return the global PostHog client configured by module settings. Most applications should either instantiate ``Posthog`` directly or set ``posthog.api_key``/other module settings before calling top-level helpers. ``setup()`` is called automatically by global APIs such as ``capture()``. Returns: The global ``Client`` instance. If both ``api_key`` and ``project_api_key`` are missing or blank, the client is disabled and module-level calls become no-ops.", + "details": "", + "category": "Initialization", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Client" + } + }, + { + "id": "shutdown", + "title": "shutdown", + "description": "Attempt final delivery and permanently shut down the global client. Call this for final process cleanup, not at the end of each request or serverless invocation that shares the module-level client. Use ``flush()`` before returning from those invocations instead. Shutdown closes capture lanes, stops background workers and the feature flag poller, and tears down integrations. The global client is not recreated automatically after shutdown. This normally blocks until queued events have been attempted and cleanup finishes. Failed or undrainable events may be dropped and reported through logging or ``on_error``; returning does not guarantee server receipt. Event draining has no timeout. Queued spans get a final flush with up to 30 seconds waiting for an in-flight flush, then a fresh 30-second drain budget; the last HTTP request is not cut short. Unsent queued spans and still-open spans are discarded with a warning. Lifecycle cleanup is attempted once, and cleanup failures are logged without retry. Calls made directly from SDK callbacks such as ``on_error`` are deferred to avoid deadlocking the worker. If blocking completion is required, signal an application-owned thread, return from the callback, and call ``shutdown()`` from that thread. Do not wait inside a callback for another thread or task calling a lifecycle method.", + "details": "", + "category": "Client management", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import shutdown\nshutdown()" + } + ] + }, + { + "id": "start_span", + "title": "start_span", + "description": "Start a span for distributed tracing. Alpha. Returns a span handle. Use it as a context manager to make it the active span for the block and end it on exit (recording a raised exception on the way out); or call ``end()`` yourself for a span that cannot wrap a block. Spans started inside the block nest under it automatically. Always returns a usable handle, even when tracing is off, so calling code never branches.", + "details": "", + "category": "Tracing", + "params": [ + { + "name": "name", + "description": "A low-cardinality operation name, e.g. ``GET /users/:id``. Variable values belong in attributes, not the name.", + "isOptional": true, + "type": "str" + }, + { + "name": "kind", + "description": "``internal`` (default), ``server``, ``client``, ``producer`` or ``consumer``.", + "isOptional": true, + "type": "str" + }, + { + "name": "attributes", + "description": "Initial attributes.", + "isOptional": true, + "type": "Mapping[str, Any]" + }, + { + "name": "parent", + "description": "A span handle, or an inbound W3C ``traceparent`` header value to continue a remote trace. Defaults to the active span.", + "isOptional": false, + "type": "Span" + }, + { + "name": "tracestate", + "description": "The inbound ``tracestate`` header accompanying a ``traceparent`` string ``parent``; preserved and propagated.", + "isOptional": true, + "type": "str" + }, + { + "name": "start_time", + "description": "A ``datetime`` or epoch seconds, to backdate the span.", + "isOptional": false, + "type": "datetime" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Span" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "import posthog\nposthog.traces = {\"service_name\": \"checkout-api\"}\n\nwith posthog.start_span(\"POST /checkout\", parent=request.headers.get(\"traceparent\")) as span:\n span.set_attribute(\"plan\", user.plan)\n with posthog.start_span(\"db.query\", kind=\"client\"):\n ...\n outgoing_headers = {\"traceparent\": span.traceparent()}" + } + ] + }, + { + "id": "tag", + "title": "tag", + "description": "Add a tag to the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "name", + "description": "The tag key", + "isOptional": true, + "type": "str" + }, + { + "name": "value", + "description": "The tag value", + "isOptional": true, + "type": "Any" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import tag\ntag(\"user_id\", \"123\")" + } + ] + } + ] + } + ], + "categories": [ + "Initialization", + "Identification", + "Capture", + "Error Tracking", + "Feature flags", + "Tracing", + "Contexts", + "Events", + "Client management" + ] +} \ No newline at end of file diff --git a/references/posthog-python-references-latest.json b/references/posthog-python-references-latest.json index ecbda11e..e6b6c343 100644 --- a/references/posthog-python-references-latest.json +++ b/references/posthog-python-references-latest.json @@ -2,7 +2,7 @@ "id": "posthog-python", "hogRef": "0.3", "info": { - "version": "7.75.6", + "version": "7.75.7", "id": "posthog-python", "title": "PostHog Python SDK", "description": "Integrate PostHog into any python application.", From 3aaba4a0c8ae036156f6becc7135907e4be835b4 Mon Sep 17 00:00:00 2001 From: Anna Garcia <11654201+turnipdabeets@users.noreply.github.com> Date: Fri, 23 Oct 2026 05:38:15 -0400 Subject: [PATCH 04/60] docs: require signed commits in contributor docs (#1111) Contributors often open PRs with unsigned commits because signing isn't documented here. (cherry picked from commit e65ed67b3a61122c6480c2198c019a493061845c) --- .github/pull_request_template.md | 1 + AGENTS.md | 1 + CONTRIBUTING.md | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index ea1215d2..61966a3b 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -9,6 +9,7 @@ ## :pencil: Checklist +- [ ] All commits are signed (see [Commit signing](https://github.com/PostHog/posthog-python/blob/main/CONTRIBUTING.md#commit-signing)) - [ ] I reviewed the submitted code. - [ ] I added tests to verify the changes. - [ ] I updated the docs if needed. diff --git a/AGENTS.md b/AGENTS.md index 0e40e181..b43427db 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -5,6 +5,7 @@ Guidance for coding agents working on the PostHog Python SDK (`posthog`). Runtim ## Start here - Use `uv`; follow [CONTRIBUTING.md](./CONTRIBUTING.md) for setup and [checks](./CONTRIBUTING.md#ci-aligned-checks). Run the smallest relevant tests first. +- Sign every commit; `main` rejects unsigned commits. Configure signing in the git config you commit with, as described in "Commit signing" in [CONTRIBUTING.md](./CONTRIBUTING.md). - For `openfeature-provider/` changes, also follow its [contributor guide](./openfeature-provider/CONTRIBUTING.md); root pytest collection does not cover it. - Read [RELEASING.md](./RELEASING.md) when adding changesets or changing builds/publishing. Releases publish both `posthog` and its generated `posthoganalytics` mirror. - For public API changes, update/review `references/public_api_snapshot.txt` and run its check as described in the contributor guide. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index bb011dfb..b7534e64 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,7 +4,7 @@ Thanks for your interest in improving the PostHog Python SDK. ## Commit signing -This repo requires all commits to be signed. To configure commit signing, see the [PostHog handbook](https://posthog.com/handbook/engineering/security#commit-signing). +This repo requires all commits to be signed. To configure commit signing, see the [PostHog handbook](https://posthog.com/handbook/engineering/security#commit-signing). Set `commit.gpgsign` to `true` in your git config so that every commit is signed, not only the ones you sign by hand. ## Setup From 8f8f57f5906b8ff11ccd72f9894053d2bd3ad15b Mon Sep 17 00:00:00 2001 From: Manoel Aranda Neto <5731772+marandaneto@users.noreply.github.com> Date: Fri, 23 Oct 2026 17:18:37 +0700 Subject: [PATCH 05/60] fix(flags): fall back for incompatible local regex evaluation (#1108) * fix(flags): fall back for incompatible local regex evaluation * fix(flags): defer nested regex quantifiers and fast-path ASCII values * fix(flags): defer strict-end searches with final newline backtracking * perf(flags): cache pure regex pattern metadata (cherry picked from commit d58380e87f38b725ded6d38b870b734056a526e7) --- .../changesets/local-regex-compatibility.md | 5 + posthog/feature_flags.py | 229 +++++++++- posthog/test/test_feature_flags_regex.py | 418 ++++++++++++++++++ 3 files changed, 646 insertions(+), 6 deletions(-) create mode 100644 .sampo/changesets/local-regex-compatibility.md create mode 100644 posthog/test/test_feature_flags_regex.py diff --git a/.sampo/changesets/local-regex-compatibility.md b/.sampo/changesets/local-regex-compatibility.md new file mode 100644 index 00000000..5bba25a5 --- /dev/null +++ b/.sampo/changesets/local-regex-compatibility.md @@ -0,0 +1,5 @@ +--- +pypi/posthog: patch +--- + +Defer feature flag regex patterns with unsupported syntax or incompatible matching semantics to server evaluation instead of returning incorrect local results. Preserve portable Unicode matching and make end-of-input anchors strict, matching the flags service. diff --git a/posthog/feature_flags.py b/posthog/feature_flags.py index 049e2fe4..66af600c 100644 --- a/posthog/feature_flags.py +++ b/posthog/feature_flags.py @@ -8,11 +8,13 @@ import warnings from decimal import Decimal from enum import Enum -from typing import Optional +from functools import lru_cache as _lru_cache +from typing import NamedTuple, NoReturn, Optional from posthog import utils as utils from posthog.types import FlagValue -from posthog.utils import convert_to_datetime_aware, is_valid_regex +from posthog.utils import convert_to_datetime_aware +from posthog.utils import is_valid_regex as is_valid_regex __LONG_SCALE__ = float(0xFFFFFFFFFFFFFFF) @@ -660,6 +662,221 @@ def _parse_numeric_value(value) -> float | None: return parsed if math.isfinite(parsed) else None +class _RegexMetadata(NamedTuple): + regex: re.Pattern[str] + uses_word_class: bool + uses_space_class: bool + uses_end_anchor: bool + uses_quantifier_or_alternation: bool + + +@_lru_cache(maxsize=256) +def _compile_regex(pattern: str) -> Optional[_RegexMetadata]: + """Cache only pattern metadata; exceptions are not retained by lru_cache.""" + # Successful re compilation alone does not establish Rust/fancy-regex compatibility. + translated = [] + class_start = None + previous = "" + group_has_quantifier = [] + closed_group_has_quantifier = False + uses_word_class = False + uses_space_class = False + uses_end_anchor = False + uses_quantifier_or_alternation = False + index = 0 + + def defer() -> NoReturn: + raise InconclusiveMatchError("Regex requires server evaluation") + + while index < len(pattern): + character = pattern[index] + if 0xD800 <= ord(character) <= 0xDFFF: + defer() + if class_start is None: + uses_end_anchor |= character == "$" + uses_quantifier_or_alternation |= character in "*+?{|" + if character == "(": + group_has_quantifier.append(False) + elif character == ")": + closed_group_has_quantifier = ( + group_has_quantifier.pop() if group_has_quantifier else False + ) + if group_has_quantifier: + group_has_quantifier[-1] |= closed_group_has_quantifier + elif character in "*+?{": + # Strict anchors can turn nested repetition into expensive backtracking. + if previous == ")" and closed_group_has_quantifier: + defer() + if group_has_quantifier: + group_has_quantifier[-1] = True + if character == "\\": + index += 1 + if index == len(pattern): + translated.append("\\") + break + escaped = pattern[index] + if escaped == "x": + digits = pattern[index + 1 : index + 3] + if len(digits) != 2 or any( + c not in "0123456789abcdefABCDEF" for c in digits + ): + defer() + translated.append("\\x" + digits) + index += 3 + previous = "literal" + continue + if escaped in "bB<>" and class_start is None: + # Even short ASCII word-boundary searches can exceed the service's step limit. + defer() + if escaped not in "dDsSwWbfnrt": + if not (0x20 <= ord(escaped) <= 0x7E and not escaped.isalnum()): + defer() + if class_start is not None and escaped not in r"\-[]^.$*+?(){}|": + defer() + uses_word_class |= escaped in "wW" + uses_space_class |= escaped in "sS" + translated.append("\\" + escaped) + previous = "literal" + elif character == "[": + if class_start is not None: + defer() + class_start = index + translated.append(character) + elif character == "]" and class_start is not None: + content_start = class_start + ( + 2 if pattern[class_start + 1 : class_start + 2] == "^" else 1 + ) + if index == content_start: + defer() + class_start = None + previous = "literal" + translated.append(character) + elif class_start is not None: + if pattern[index : index + 2] in ("&&", "--", "~~"): + defer() + # Rust and Python disagree on shorthand endpoints in ranges. + if ( + character == "-" + and index + 1 < len(pattern) + and pattern[index + 1] != "]" + ): + if pattern[index + 1 : index + 3] in ( + r"\d", + r"\D", + r"\s", + r"\S", + r"\w", + r"\W", + ) or ( + index >= 2 + and pattern[index - 2] == "\\" + and pattern[index - 1] in "dDsSwW" + ): + defer() + translated.append(character) + elif character == "(" and pattern[index + 1 : index + 2] == "?": + if pattern[index + 2 : index + 3] != ":": + defer() + translated.append("(?:") + index += 2 + previous = "(" + elif character == "{": + repetition = re.match(r"\{([0-9]+)(?:,([0-9]*))?\}", pattern[index:]) + if repetition is None or previous in ( + "", + "|", + "(", + "^", + "$", + "empty group", + "quantifier", + ): + defer() + if any( + len(n) > 10 or int(n) > 0xFFFFFFFF for n in repetition.groups() if n + ): + defer() + translated.append(repetition.group()) + index += len(repetition.group()) - 1 + if pattern[index + 1 : index + 2] in ("+", "{"): + defer() + previous = "quantifier" + else: + if character in "*+?": + if previous in ("^", "$", "empty group") or pattern[ + index + 1 : index + 2 + ] in ("+", "{"): + defer() + previous = "quantifier" + else: + previous = ( + "empty group" + if character == ")" and previous in ("(", "empty group") + else character + ) + # Python's $ also matches before a final newline; Rust's default $ is strict. + translated.append(r"\Z" if character == "$" else character) + index += 1 + + try: + regex = re.compile("".join(translated)) + except re.error: + return None + except Exception as error: + raise InconclusiveMatchError( + "Regex compilation requires server evaluation" + ) from error + + return _RegexMetadata( + regex, + uses_word_class, + uses_space_class, + uses_end_anchor, + uses_quantifier_or_alternation, + ) + + +def _match_regex(pattern: str, value: str) -> Optional[bool]: + """Return None for invalid shared syntax; raise inconclusive for unsupported cases.""" + if pattern == "": + return True + metadata = _compile_regex(pattern) + if metadata is None: + return None + ( + regex, + uses_word_class, + uses_space_class, + uses_end_anchor, + uses_quantifier_or_alternation, + ) = metadata + + def defer() -> NoReturn: + raise InconclusiveMatchError("Regex requires server evaluation") + + # Tightening $ can expose backtracking on a final LF, including repeated alternatives. + if uses_end_anchor and uses_quantifier_or_alternation and value.endswith("\n"): + defer() + + # Unicode letters/decimal digits agree, but Rust's word class also includes marks and + # joiners, while Python includes other numbers. Python's whitespace adds U+001C..U+001F. + if not value.isascii(): + if any(0xD800 <= ord(c) <= 0xDFFF for c in value): + defer() + if uses_word_class and any( + not c.isascii() and not (c.isalpha() or c.isdecimal()) for c in value + ): + defer() + if uses_space_class and any(c in value for c in "\x1c\x1d\x1e\x1f"): + defer() + try: + return regex.search(value) is not None + except Exception as error: + raise InconclusiveMatchError( + "Regex execution requires server evaluation" + ) from error + + def match_property( property, property_values, property_matching_version: int = 1 ) -> bool: @@ -752,11 +969,11 @@ def compute_exact_match(value, override_value): return matched if operator in ("regex", "not_regex"): - if not is_valid_regex(str(value)): - return False property_string = "null" if override_value is None else str(override_value) - matched = re.compile(str(value)).search(property_string) is not None - return not matched if operator == "not_regex" else matched + regex_match = _match_regex(str(value), property_string) + if regex_match is None: + return False + return not regex_match if operator == "not_regex" else regex_match if operator in ["is_date_before", "is_date_after"]: try: diff --git a/posthog/test/test_feature_flags_regex.py b/posthog/test/test_feature_flags_regex.py new file mode 100644 index 00000000..5819aacc --- /dev/null +++ b/posthog/test/test_feature_flags_regex.py @@ -0,0 +1,418 @@ +"""Regex compatibility with the flags service's Rust matcher, not just Python re.""" + +from unittest import mock + +import pytest + +from posthog.client import Client +from posthog.feature_flags import ( + InconclusiveMatchError, + match_cohort, + match_property, + match_property_group, +) +from posthog.test.test_utils import FAKE_TEST_API_KEY + + +pytestmark = pytest.mark.filterwarnings( + r"ignore:`get_feature_flag` is deprecated:DeprecationWarning" +) + + +# These constructs either have different native semantics or require service syntax/budgets. +DEFERRED = [ + (r"^\p{L}+$", "é"), + (r"^\p{L}+$", None), + (r"^\P{L}+$", "123"), + (r"(?<=a+)b", "aab"), + (r"a(?=b)", "ab"), + (r"(a)\1", "aa"), + (r"(?i)^foo$", "FOO"), + (r"(?m)^foo$", "foo\nbar"), + (r"\Afoo\z", "foo"), + (r"^[a&&b]$", "a"), + (r"^[a-z&&[^aeiou]]+$", "bcdf"), + (r"^[a-z--b]$", "b"), + (r"^[a~~b]$", "~"), + (r"^[[:alpha:]]+$", "foo"), + (r"[]", "a"), + (r"[^]", "a"), + (r"\141", "a"), + (r"\q", "q"), + (r"^\x{41}$", "A"), + (r"a++", "aa"), + (r"a{,2}", "aa"), + (r"a*{2}", "aa"), + (r"{foo}", "{foo}"), + (r"[\s-9]", "-"), + (r"[a-\d]", "-"), + (r"^?https?://", "https://a"), + (r"$?", "x"), + (r"\b+", "x"), + (r"{2}", "x"), + (r"a|{2}", "x"), + (r"({2})", "x"), + (r"(?:{2})", "x"), + (r"(?:)*", "x"), + (r"()+", "x"), + (r"(?:){2}", "x"), + (r"a{0,4294967296}", "x"), + (r"a{4294967296,}", "x"), + (r".*\bbot\b.*", "a" * 140), + (r".*\Bbot\B.*", "a" * 140), + (r"\", "foo"), + (r"[\@]", "@"), + (r"[\B]", "B"), + (r"^\w+$", "\u0301"), # Rust includes combining marks; Python does not. + (r"^\W+$", "²"), # Python includes non-decimal numbers; Rust does not. + (r"^\s$", "\x1c"), # Python's extra ASCII whitespace is not Rust whitespace. + (r"^\S$", "\x1f"), + ("^.$", "\ud800"), + ("\ud800", "x"), + (r"^(a+)+$", "aa\n"), + (r"^(a+)+$", "aa"), + (r"^(?:ab*)+$", "ab"), + (r"^(a{1,2}){2}$", "aa"), + (r"^(a?b)*$", "ab"), + (r"^(a+|b)+$", "a"), + (r"^((a+))+$", "aa"), + (r"^((a+)b)+$", "ab"), + (r"^((a+)*b)+$", "aab"), + (r"^(a+)+?$", "aa"), + (r"(a+)+", "aa"), + (r"^\s$", "\x1d"), + (r"^\S$", "\x1e"), + (r"^\s+$", "\u2003\x1c"), + (r"^\w+$", "a\u0301"), + ("^a.$", "a\ud800"), + (r"^(a|aa)+$", "aa\n"), + (r"^a+$", "aa\n"), + (r"^a*$", "a\n"), + (r"^a?$", "a\n"), + (r"^a{2}$", "aa\n"), + (r"^(?:ab|cd){2,3}$", "abcd\n"), + (r"^(?:foo|bar)$", "foo\n"), + (r"foo|bar$", "foo\n"), +] + +LOCAL = [ + ("pro", "a pro plan", True), + ("^pro$", "a pro plan", False), + ("^pro$", "PRO", False), + (r"^\d+$", "123", True), + (r"^\d+$", "١", True), + (r"^\D+$", "é", True), + (r"^\w+$", "abc_123", True), + (r"^\w+$", "é١", True), + (r"^\W+$", "é١", False), + (r"^\s$", "\u2003", True), + (r"^\s$", "\ufeff", False), + ("^.$", "😀", True), + ("^é+$", "éé", True), + ("^.$", "\r", True), + (r"^[\w.+-]+@example\.com$", "a.b+tag@example.com", True), + (r"^(?:ab|cd){2,3}$", "abcdab", True), + (r"^\\p\{L\}$", r"\p{L}", True), + (r"^\(\?i\)$", "(?i)", True), + (r"^\x41+$", "AA", True), + (r"^nu", None, True), + (r"^\++$", "++", True), + (r"[\b]", "\b", True), + (r"^a\-b$", "a-b", True), + (r"\@example\.com$", "a@example.com", True), + (r"^a\:b$", "a:b", True), + (r"^a\_b$", "a_b", True), + (r"^a\=b$", "a=b", True), + (r"^a\ b$", "a b", True), + (r"^\^?$", "", True), + (r"^\${2}$", "$$", True), + (r"^\({2}$", "((", True), + (r"^\|{2}$", "||", True), + (r"^[\^$?]+$", "$?^", True), + (r"^[\w-]+$", "a-b", True), + ("", "", True), + ("", "é", True), + ("", None, True), + ("", "\r", True), + ("^foo$", "foo\n", False), + ("^foo$", "foo\nbar", False), + ("foo$", "foo\r\n", False), + (r"^([+*?])+$", "+*?", True), + (r"^(\++)$", "++", True), + (r"^(\+)+$", "++", True), + (r"^\(+a+\)+$", "(aa)", True), + (r"^(a+)(b+)$", "aabb", True), + (r"^((ab)){2}$", "abab", True), + (r"^([()]+)$", "()", True), + (r"^(a)+(b)+$", "aabb", True), + (r"^((a)+b)$", "aab", True), + (r"^(a+)(?:b)+$", "aabb", True), + ("", "\u0301", True), + ("^$", "", True), + (r"^[+*?{}|]$", "+\n", False), + (r"^\+$", "+\n", False), + (r"^\|$", "|\n", False), + (r"^a\{$", "a{\n", False), + (r"^a+\$", "aa$\n", True), + (r"^foo\$$", "foo$\n", False), + (r"^a+", "aa\n", True), + (r"^(?:a|aa)+$", "aa", True), +] + + +def property_filter(pattern, operator): + return {"key": "value", "type": "person", "operator": operator, "value": pattern} + + +@pytest.mark.parametrize("operator", ["regex", "not_regex"]) +@pytest.mark.parametrize("pattern,value", DEFERRED) +def test_match_property_regex_defers_unsupported(pattern, value, operator): + with pytest.raises(InconclusiveMatchError): + match_property(property_filter(pattern, operator), {"value": value}) + + +@pytest.mark.parametrize("operator", ["regex", "not_regex"]) +@pytest.mark.parametrize("pattern,value,expected", LOCAL) +def test_match_property_regex_keeps_portable_patterns_local( + pattern, value, expected, operator +): + assert match_property(property_filter(pattern, operator), {"value": value}) is ( + expected if operator == "regex" else not expected + ) + + +@pytest.mark.parametrize("operator", ["regex", "not_regex"]) +@pytest.mark.parametrize("pattern", ["[", "(", "*", "?*"]) +@pytest.mark.parametrize("value", ["value", "é", "\r"]) +def test_match_property_regex_invalid_is_false_for_both_operators( + pattern, value, operator +): + assert match_property(property_filter(pattern, operator), {"value": value}) is False + + +@pytest.mark.parametrize("operator", ["regex", "not_regex"]) +@pytest.mark.parametrize("pattern", [r"*foo$", r"(a|b$"]) +def test_match_property_regex_invalid_precedes_final_newline_policy(pattern, operator): + assert ( + match_property(property_filter(pattern, operator), {"value": "foo\n"}) is False + ) + + +@pytest.mark.parametrize("operator", ["regex", "not_regex"]) +def test_match_property_regex_compile_resource_failure_is_inconclusive(operator): + with pytest.raises(InconclusiveMatchError): + match_property( + property_filter("(" * 1000 + "a" + ")" * 1000, operator), {"value": "a"} + ) + + +@pytest.mark.parametrize("operator", ["regex", "not_regex"]) +@pytest.mark.parametrize("negation", [False, True]) +@pytest.mark.parametrize("pattern", [r"\p{L}+", r"(é+)+"]) +def test_match_property_regex_cohort_does_not_negate_inconclusive( + operator, negation, pattern +): + prop = {**property_filter(pattern, operator), "negation": negation} + with pytest.raises(InconclusiveMatchError): + match_property_group({"type": "AND", "values": [prop]}, {"value": "é"}, {}) + + +@pytest.mark.parametrize("operator", ["regex", "not_regex"]) +@pytest.mark.parametrize( + "pattern,value", + [(r"\p{L}+", "é"), (r".*\bbot\b.*", "a" * 140), (r"^(a+)+$", "aa\n")], +) +def test_match_cohort_regex_not_in_does_not_negate_inconclusive( + operator, pattern, value +): + cohorts = {"1": {"type": "AND", "values": [property_filter(pattern, operator)]}} + with pytest.raises(InconclusiveMatchError): + match_cohort( + {"value": 1, "operator": "not_in", "type": "cohort"}, + {"value": value}, + cohorts, + ) + + +@pytest.mark.parametrize("operator", ["regex", "not_regex"]) +@pytest.mark.parametrize( + "pattern,cases", + [ + (r"^\w+$", [("abc", True), ("a\u0301", "defer"), ("!", False), ("é١", True)]), + ( + r"^\s+$", + [ + (" ", True), + ("\x1c", "defer"), + ("\u2003", True), + ("\u2003\x1c", "defer"), + ("\ufeff", False), + ], + ), + (r"^a+$", [("aa", True), ("aa\n", "defer"), ("b", False)]), + (r"^.$", [("😀", True), ("\ud800", "defer"), ("\n", False), ("\r", True)]), + (r"^é+$", [("é", True), ("é\n", "defer"), ("foo", False)]), + ("[", [("value", "invalid"), ("value\n", "invalid"), ("\ud800", "invalid")]), + (r"\p{L}", [("abc", "defer"), ("é", "defer"), ("\n", "defer")]), + ], +) +def test_match_property_regex_same_pattern_different_values(pattern, cases, operator): + for sequence in (cases, list(reversed(cases))): + for value, expected in sequence: + prop = property_filter(pattern, operator) + if expected == "defer": + with pytest.raises(InconclusiveMatchError): + match_property(prop, {"value": value}) + elif expected == "invalid": + assert match_property(prop, {"value": value}) is False + else: + assert match_property(prop, {"value": value}) is ( + expected if operator == "regex" else not expected + ) + + +@pytest.fixture +def regex_pattern_cache(): + from posthog.feature_flags import _compile_regex + + _compile_regex.cache_clear() + yield _compile_regex + _compile_regex.cache_clear() + + +def test_regex_pattern_cache_reuses_metadata_not_match_results(regex_pattern_cache): + prop = property_filter("^cached$", "regex") + assert match_property(prop, {"value": "cached"}) is True + assert match_property(prop, {"value": "other"}) is False + assert ( + match_property(property_filter("^cached$", "not_regex"), {"value": "other"}) + is True + ) + info = regex_pattern_cache.cache_info() + assert (info.misses, info.hits, info.currsize) == (1, 2, 1) + metadata = regex_pattern_cache("^cached$") + assert regex_pattern_cache("^cached$") is metadata + with pytest.raises(AttributeError): + metadata.uses_word_class = True + + +def test_regex_pattern_cache_is_bounded_and_evicts_least_recently_used( + regex_pattern_cache, +): + first = regex_pattern_cache("^cache-first$") + second = regex_pattern_cache("^cache-second$") + limit = regex_pattern_cache.cache_info().maxsize + assert limit == 256 + for index in range(limit - 2): + regex_pattern_cache(f"^cache-{index}$") + assert regex_pattern_cache("^cache-first$") is first + regex_pattern_cache("^cache-overflow$") + info = regex_pattern_cache.cache_info() + assert info.currsize == limit + assert regex_pattern_cache("^cache-first$") is first + assert regex_pattern_cache.cache_info().misses == info.misses + assert regex_pattern_cache("^cache-second$") is not second + assert regex_pattern_cache.cache_info().misses == info.misses + 1 + assert regex_pattern_cache.cache_info().currsize == limit + + +def test_regex_pattern_cache_reuses_invalid_classification(regex_pattern_cache): + assert regex_pattern_cache("[") is None + for operator in ("regex", "not_regex"): + assert match_property(property_filter("[", operator), {"value": "x\n"}) is False + info = regex_pattern_cache.cache_info() + assert (info.misses, info.hits, info.currsize) == (1, 2, 1) + + +def test_regex_pattern_cache_does_not_retain_exceptions(regex_pattern_cache): + errors = [] + for _ in range(2): + with pytest.raises(InconclusiveMatchError) as raised: + regex_pattern_cache(r"\p{L}") + errors.append(raised.value) + assert errors[0] is not errors[1] + info = regex_pattern_cache.cache_info() + assert (info.misses, info.hits, info.currsize) == (2, 0, 0) + + +def test_regex_empty_pattern_bypasses_pattern_cache(regex_pattern_cache): + for operator in ("regex", "not_regex"): + for value in ("", "é", "\n"): + assert match_property(property_filter("", operator), {"value": value}) is ( + operator == "regex" + ) + info = regex_pattern_cache.cache_info() + assert (info.misses, info.hits, info.currsize) == (0, 0, 0) + + +@pytest.fixture +def client(): + instance = Client(FAKE_TEST_API_KEY, send=False) + yield instance + instance.shutdown() + + +@pytest.mark.parametrize("api", ["evaluate_flags", "get_feature_flag"]) +@pytest.mark.parametrize("operator", ["regex", "not_regex"]) +def test_regex_public_fallback_boundaries(client, api, operator): + cases = [(p, {"value": v}, None) for p, v in DEFERRED] + cases += [ + (p, {"value": v}, expected if operator == "regex" else not expected) + for p, v, expected in LOCAL + ] + cases += [ + ("[", {"value": "x"}, False), + (r"*foo$", {"value": "foo\n"}, False), + (r"(a|b$", {"value": "foo\n"}, False), + ("safe", {}, None), + ] + with ( + mock.patch("posthog.client.flags") as remote, + mock.patch.object(Client, "capture"), + ): + remote.return_value = { + "flags": {"regex-flag": {"enabled": True, "variant": None}} + } + for pattern, properties, local_result in cases: + client.feature_flags = [ + { + "id": 1, + "key": "regex-flag", + "active": True, + "filters": { + "groups": [ + { + "properties": [property_filter(pattern, operator)], + "rollout_percentage": 100, + } + ] + }, + } + ] + for local_only in (False, True): + remote.reset_mock() + kwargs = { + "person_properties": properties, + "only_evaluate_locally": local_only, + } + if api == "evaluate_flags": + result = client.evaluate_flags("user-1", **kwargs).get_flag( + "regex-flag" + ) + else: + result = client.get_feature_flag( + "regex-flag", "user-1", send_feature_flag_events=False, **kwargs + ) + expected = ( + (None if local_only else True) + if local_result is None + else local_result + ) + assert result is expected, ( + api, + operator, + pattern, + properties, + local_only, + ) + assert remote.call_count == int(local_result is None and not local_only) From 81c420c45f1aebe1ea1f555b49f63684233cd006 Mon Sep 17 00:00:00 2001 From: "releaser-posthog-python[bot]" <262414804+releaser-posthog-python[bot]@users.noreply.github.com> Date: Fri, 23 Oct 2026 10:22:51 +0000 Subject: [PATCH 06/60] chore: Release v7.75.8 [skip ci] (cherry picked from commit 72e013122e07345a827d108de74f079672220f1d) --- .sampo/changesets/local-regex-compatibility.md | 5 ----- openfeature-provider/CHANGELOG.md | 6 ++++++ openfeature-provider/pyproject.toml | 2 +- posthog/CHANGELOG.md | 6 ++++++ posthog/version.py | 2 +- pyproject.toml | 2 +- uv.lock | 4 ++-- 7 files changed, 17 insertions(+), 10 deletions(-) delete mode 100644 .sampo/changesets/local-regex-compatibility.md diff --git a/.sampo/changesets/local-regex-compatibility.md b/.sampo/changesets/local-regex-compatibility.md deleted file mode 100644 index 5bba25a5..00000000 --- a/.sampo/changesets/local-regex-compatibility.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -pypi/posthog: patch ---- - -Defer feature flag regex patterns with unsupported syntax or incompatible matching semantics to server evaluation instead of returning incorrect local results. Preserve portable Unicode matching and make end-of-input anchors strict, matching the flags service. diff --git a/openfeature-provider/CHANGELOG.md b/openfeature-provider/CHANGELOG.md index 1c3d0efc..41701c15 100644 --- a/openfeature-provider/CHANGELOG.md +++ b/openfeature-provider/CHANGELOG.md @@ -4,6 +4,12 @@ All notable changes to `openfeature-provider-posthog` are documented here. This file is maintained by [Sampo](https://github.com/bruits/sampo) from changesets in `.sampo/changesets/` that target `pypi/openfeature-provider-posthog`. +## 0.1.122 — 2026-10-23 + +### Patch changes + +- Updated dependencies: posthog@7.75.8 + ## 0.1.121 — 2026-10-23 ### Patch changes diff --git a/openfeature-provider/pyproject.toml b/openfeature-provider/pyproject.toml index fa8e6cfc..f96e47bc 100644 --- a/openfeature-provider/pyproject.toml +++ b/openfeature-provider/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "openfeature-provider-posthog" -version = "0.1.121" +version = "0.1.122" description = "Official PostHog provider for the OpenFeature Python SDK." readme = "README.md" authors = [{ name = "PostHog", email = "engineering@posthog.com" }] diff --git a/posthog/CHANGELOG.md b/posthog/CHANGELOG.md index ef480700..5daf73be 100644 --- a/posthog/CHANGELOG.md +++ b/posthog/CHANGELOG.md @@ -1,5 +1,11 @@ # posthog +## 7.75.8 — 2026-10-23 + +### Patch changes + +- [d58380e](https://github.com/posthog/posthog-python/commit/d58380e87f38b725ded6d38b870b734056a526e7) Defer feature flag regex patterns with unsupported syntax or incompatible matching semantics to server evaluation instead of returning incorrect local results. Preserve portable Unicode matching and make end-of-input anchors strict, matching the flags service. — Thanks @marandaneto! + ## 7.75.7 — 2026-10-23 ### Patch changes diff --git a/posthog/version.py b/posthog/version.py index 49289dc3..42ba1010 100644 --- a/posthog/version.py +++ b/posthog/version.py @@ -1 +1 @@ -VERSION = "7.75.7" +VERSION = "7.75.8" diff --git a/pyproject.toml b/pyproject.toml index 3248536f..b9442c52 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "posthog" -version = "7.75.7" +version = "7.75.8" description = "Integrate PostHog into any python application." authors = [{ name = "PostHog", email = "engineering@posthog.com" }] maintainers = [{ name = "PostHog", email = "engineering@posthog.com" }] diff --git a/uv.lock b/uv.lock index 51344c2f..8dbc9e20 100644 --- a/uv.lock +++ b/uv.lock @@ -2656,7 +2656,7 @@ wheels = [ [[package]] name = "openfeature-provider-posthog" -version = "0.1.121" +version = "0.1.122" source = { editable = "openfeature-provider" } dependencies = [ { name = "openfeature-sdk" }, @@ -3015,7 +3015,7 @@ wheels = [ [[package]] name = "posthog" -version = "7.75.7" +version = "7.75.8" source = { editable = "." } dependencies = [ { name = "distro" }, From a5a43260b8932d3d9b91644420c4d65e74cf8a2a Mon Sep 17 00:00:00 2001 From: "releaser-posthog-python[bot]" <262414804+releaser-posthog-python[bot]@users.noreply.github.com> Date: Fri, 23 Oct 2026 10:22:57 +0000 Subject: [PATCH 07/60] Update generated references (cherry picked from commit a020e703fd89e4e35211419ad6f1ba3b656d798f) --- .../posthog-python-references-7.75.8.json | 3548 +++++++++++++++++ .../posthog-python-references-latest.json | 2 +- 2 files changed, 3549 insertions(+), 1 deletion(-) create mode 100644 references/posthog-python-references-7.75.8.json diff --git a/references/posthog-python-references-7.75.8.json b/references/posthog-python-references-7.75.8.json new file mode 100644 index 00000000..bacd9eaf --- /dev/null +++ b/references/posthog-python-references-7.75.8.json @@ -0,0 +1,3548 @@ +{ + "id": "posthog-python", + "hogRef": "0.3", + "info": { + "version": "7.75.8", + "id": "posthog-python", + "title": "PostHog Python SDK", + "description": "Integrate PostHog into any python application.", + "slugPrefix": "posthog-python", + "specUrl": "https://github.com/PostHog/posthog-python" + }, + "types": [ + { + "id": "FeatureFlag", + "name": "FeatureFlag", + "path": "posthog.types.FeatureFlag", + "properties": [ + { + "name": "key", + "type": "str", + "description": "Field: key" + }, + { + "name": "enabled", + "type": "bool", + "description": "Field: enabled" + }, + { + "name": "variant", + "type": "Optional[str]", + "description": "Field: variant" + }, + { + "name": "reason", + "type": "Optional[FlagReason]", + "description": "Field: reason" + }, + { + "name": "metadata", + "type": "Union[FlagMetadata, LegacyFlagMetadata]", + "description": "Field: metadata" + } + ], + "example": "" + }, + { + "id": "FeatureFlagResult", + "name": "FeatureFlagResult", + "path": "posthog.types.FeatureFlagResult", + "properties": [ + { + "name": "key", + "type": "str", + "description": "Field: key" + }, + { + "name": "enabled", + "type": "bool", + "description": "Field: enabled" + }, + { + "name": "variant", + "type": "Optional[str]", + "description": "Field: variant" + }, + { + "name": "payload", + "type": "Optional[Any]", + "description": "Field: payload" + }, + { + "name": "reason", + "type": "Optional[str]", + "description": "Field: reason" + } + ], + "example": "" + }, + { + "id": "FlagMetadata", + "name": "FlagMetadata", + "path": "posthog.types.FlagMetadata", + "properties": [ + { + "name": "id", + "type": "int", + "description": "Field: id" + }, + { + "name": "payload", + "type": "Optional[str]", + "description": "Field: payload" + }, + { + "name": "version", + "type": "int", + "description": "Field: version" + }, + { + "name": "description", + "type": "str", + "description": "Field: description" + }, + { + "name": "has_experiment", + "type": "Optional[bool]", + "description": "Field: has_experiment" + } + ], + "example": "" + }, + { + "id": "FlagReason", + "name": "FlagReason", + "path": "posthog.types.FlagReason", + "properties": [ + { + "name": "code", + "type": "str", + "description": "Field: code" + }, + { + "name": "condition_index", + "type": "Optional[int]", + "description": "Field: condition_index" + }, + { + "name": "description", + "type": "str", + "description": "Field: description" + } + ], + "example": "" + }, + { + "id": "FlagsAndPayloads", + "name": "FlagsAndPayloads", + "path": "posthog.types.FlagsAndPayloads", + "properties": [ + { + "name": "featureFlags", + "type": "Optional[dict[str, Union[bool, str]]]", + "description": "Field: featureFlags" + }, + { + "name": "featureFlagPayloads", + "type": "Optional[dict[str, Any]]", + "description": "Field: featureFlagPayloads" + } + ], + "example": "" + }, + { + "id": "FlagsResponse", + "name": "FlagsResponse", + "path": "posthog.types.FlagsResponse", + "properties": [ + { + "name": "flags", + "type": "dict[str, FeatureFlag]", + "description": "Field: flags" + }, + { + "name": "errorsWhileComputingFlags", + "type": "bool", + "description": "Field: errorsWhileComputingFlags" + }, + { + "name": "requestId", + "type": "str", + "description": "Field: requestId" + }, + { + "name": "quotaLimit", + "type": "Optional[list[str]]", + "description": "Field: quotaLimit" + }, + { + "name": "evaluatedAt", + "type": "Optional[int]", + "description": "Field: evaluatedAt" + }, + { + "name": "minimalFlagCalledEvents", + "type": "bool", + "description": "Field: minimalFlagCalledEvents" + } + ], + "example": "" + }, + { + "id": "LegacyFlagMetadata", + "name": "LegacyFlagMetadata", + "path": "posthog.types.LegacyFlagMetadata", + "properties": [ + { + "name": "payload", + "type": "Any", + "description": "Field: payload" + } + ], + "example": "" + }, + { + "id": "SendFeatureFlagsOptions", + "name": "SendFeatureFlagsOptions", + "path": "posthog.types.SendFeatureFlagsOptions", + "properties": [ + { + "name": "should_send", + "type": "bool", + "description": "Field: should_send" + }, + { + "name": "only_evaluate_locally", + "type": "Optional[bool]", + "description": "Field: only_evaluate_locally" + }, + { + "name": "person_properties", + "type": "Optional[dict[str, Any]]", + "description": "Field: person_properties" + }, + { + "name": "group_properties", + "type": "Optional[dict[str, dict[str, Any]]]", + "description": "Field: group_properties" + }, + { + "name": "flag_keys_filter", + "type": "Optional[list[str]]", + "description": "Field: flag_keys_filter" + } + ], + "example": "" + }, + { + "id": "OptionalCaptureArgs", + "name": "OptionalCaptureArgs", + "path": "posthog.args.OptionalCaptureArgs", + "properties": [ + { + "name": "distinct_id", + "type": "NotRequired[Union[Number, str, UUID, int, any]]", + "description": "Field: distinct_id" + }, + { + "name": "properties", + "type": "NotRequired[Optional[dict[str, Any]]]", + "description": "Field: properties" + }, + { + "name": "timestamp", + "type": "NotRequired[Union[datetime, str, any]]", + "description": "Field: timestamp" + }, + { + "name": "uuid", + "type": "NotRequired[Union[str, UUID, any]]", + "description": "Field: uuid" + }, + { + "name": "groups", + "type": "NotRequired[Optional[dict[str, str]]]", + "description": "Field: groups" + }, + { + "name": "flags", + "type": "NotRequired[Optional[ForwardRef('FeatureFlagEvaluations')]]", + "description": "Field: flags" + }, + { + "name": "send_feature_flags", + "type": "NotRequired[Union[bool, SendFeatureFlagsOptions, any]]", + "description": "Field: send_feature_flags" + }, + { + "name": "disable_geoip", + "type": "NotRequired[Optional[bool]]", + "description": "Field: disable_geoip" + }, + { + "name": "_property_allowlist", + "type": "NotRequired[Optional[frozenset[str]]]", + "description": "Field: _property_allowlist" + } + ], + "example": "" + }, + { + "id": "OptionalSetArgs", + "name": "OptionalSetArgs", + "path": "posthog.args.OptionalSetArgs", + "properties": [ + { + "name": "distinct_id", + "type": "NotRequired[Union[Number, str, UUID, int, any]]", + "description": "Field: distinct_id" + }, + { + "name": "properties", + "type": "NotRequired[Optional[dict[str, Any]]]", + "description": "Field: properties" + }, + { + "name": "timestamp", + "type": "NotRequired[Union[datetime, str, any]]", + "description": "Field: timestamp" + }, + { + "name": "uuid", + "type": "NotRequired[Union[str, UUID, any]]", + "description": "Field: uuid" + }, + { + "name": "disable_geoip", + "type": "NotRequired[Optional[bool]]", + "description": "Field: disable_geoip" + } + ], + "example": "" + }, + { + "id": "SendFeatureFlagsOptions", + "name": "SendFeatureFlagsOptions", + "path": "posthog.types.SendFeatureFlagsOptions", + "properties": [ + { + "name": "should_send", + "type": "bool", + "description": "Field: should_send" + }, + { + "name": "only_evaluate_locally", + "type": "Optional[bool]", + "description": "Field: only_evaluate_locally" + }, + { + "name": "person_properties", + "type": "Optional[dict[str, Any]]", + "description": "Field: person_properties" + }, + { + "name": "group_properties", + "type": "Optional[dict[str, dict[str, Any]]]", + "description": "Field: group_properties" + }, + { + "name": "flag_keys_filter", + "type": "Optional[list[str]]", + "description": "Field: flag_keys_filter" + } + ], + "example": "" + } + ], + "classes": [ + { + "id": "PostHog", + "title": "PostHog", + "description": "This is the SDK reference for the PostHog Python SDK. You can learn more about example usage in the [Python SDK documentation](/docs/libraries/python). You can also follow [Flask](/docs/libraries/flask) and [Django](/docs/libraries/django) guides to integrate PostHog into your project. For long-running applications, create one client during application startup and reuse it for the lifetime of the process. This keeps background queues predictable and makes shutdown flushing straightforward. Multiple clients are still supported for intentional multi-project or multi-host setups.", + "functions": [ + { + "id": "__init__", + "title": "Client", + "description": "Initialize a new PostHog client instance.", + "details": "", + "category": "Initialization", + "params": [ + { + "name": "project_api_key", + "description": "PostHog project API key/token.", + "isOptional": true, + "type": "str" + }, + { + "name": "host", + "description": "PostHog host. Defaults to the US ingestion endpoint when not set. App hosts such as ``https://us.posthog.com`` are mapped to the corresponding ingestion host.", + "isOptional": false, + "type": "any" + }, + { + "name": "debug", + "description": "Enable verbose SDK logging and re-raise errors from public API methods.", + "isOptional": false, + "type": "bool" + }, + { + "name": "max_queue_size", + "description": "Maximum number of events buffered before upload.", + "isOptional": false, + "type": "int" + }, + { + "name": "send", + "description": "If False, queueing succeeds but events are not sent.", + "isOptional": false, + "type": "bool" + }, + { + "name": "on_error", + "description": "Optional callback invoked by background consumers when an upload fails. Keep it short and non-blocking. Calling lifecycle methods directly is safe and deferred, but do not start another thread or task that calls ``flush()``, ``join()``, or ``shutdown()`` and then wait for it from the callback.", + "isOptional": false, + "type": "any" + }, + { + "name": "flush_at", + "description": "Number of queued events that triggers a batch upload.", + "isOptional": false, + "type": "int" + }, + { + "name": "flush_interval", + "description": "Maximum seconds a background consumer waits before flushing a partial batch.", + "isOptional": false, + "type": "float" + }, + { + "name": "gzip", + "description": "Whether to gzip event upload payloads.", + "isOptional": false, + "type": "bool" + }, + { + "name": "max_retries", + "description": "Number of upload retries. Values below 0 are treated as 0.", + "isOptional": false, + "type": "int" + }, + { + "name": "sync_mode", + "description": "If True, send each event synchronously instead of using background worker threads. This blocks the calling thread; in asyncio applications such as FastAPI, use ``AsyncPosthog`` instead.", + "isOptional": false, + "type": "bool" + }, + { + "name": "timeout", + "description": "HTTP request timeout in seconds for event uploads.", + "isOptional": false, + "type": "float" + }, + { + "name": "thread", + "description": "Number of background consumer threads.", + "isOptional": false, + "type": "int" + }, + { + "name": "poll_interval", + "description": "Seconds between local feature flag definition refreshes.", + "isOptional": false, + "type": "int" + }, + { + "name": "personal_api_key", + "description": "Deprecated alias for ``secret_key``. Still honored for backwards compatibility; prefer ``secret_key``, which also accepts a Project Secret API Key.", + "isOptional": false, + "type": "any" + }, + { + "name": "disabled", + "description": "If True, disable captures and API requests. Useful in tests.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable server-side GeoIP enrichment. Defaults to True.", + "isOptional": false, + "type": "bool" + }, + { + "name": "is_server", + "description": "Whether events are emitted from a server-side runtime. Defaults to True; set to False when using the SDK as a client/CLI so the device OS is attributed to the person normally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "historical_migration", + "description": "Mark events as historical migration imports.", + "isOptional": false, + "type": "bool" + }, + { + "name": "feature_flags_request_timeout_seconds", + "description": "Timeout in seconds for feature flag and remote config requests.", + "isOptional": false, + "type": "float" + }, + { + "name": "feature_flags_request_max_retries", + "description": "Number of retries for feature flag requests after network, transport, or timeout failures. Defaults to 1. Set to 0 to disable retries.", + "isOptional": false, + "type": "int" + }, + { + "name": "super_properties", + "description": "Properties merged into every captured event.", + "isOptional": false, + "type": "any" + }, + { + "name": "enable_exception_autocapture", + "description": "Automatically capture uncaught exceptions.", + "isOptional": false, + "type": "bool" + }, + { + "name": "log_captured_exceptions", + "description": "Also log exceptions captured by error tracking.", + "isOptional": false, + "type": "bool" + }, + { + "name": "project_root", + "description": "Root path used to determine in-app stack frames for captured exceptions. Defaults to the current working directory.", + "isOptional": false, + "type": "any" + }, + { + "name": "privacy_mode", + "description": "For AI observability, capture usage metadata without prompt inputs or outputs.", + "isOptional": false, + "type": "bool" + }, + { + "name": "before_send", + "description": "Optional callback that can modify or drop events before upload. Return ``None`` to drop an event.", + "isOptional": false, + "type": "any" + }, + { + "name": "flag_fallback_cache_url", + "description": "Optional feature flag fallback cache URL, such as ``memory://local/?ttl=300&size=10000`` or a Redis URL.", + "isOptional": false, + "type": "any" + }, + { + "name": "enable_local_evaluation", + "description": "Whether to poll feature flag definitions for local evaluation when a personal API key is configured.", + "isOptional": false, + "type": "bool" + }, + { + "name": "flag_definition_cache_provider", + "description": "Optional external cache provider for sharing feature flag definitions across workers.", + "isOptional": true, + "type": "FlagDefinitionCacheProvider" + }, + { + "name": "capture_exception_code_variables", + "description": "Capture local variable values on exception stack frames.", + "isOptional": false, + "type": "bool" + }, + { + "name": "code_variables_mask_patterns", + "description": "Variable-name patterns to mask when capturing code variables.", + "isOptional": false, + "type": "any" + }, + { + "name": "code_variables_ignore_patterns", + "description": "Variable-name patterns to omit when capturing code variables.", + "isOptional": false, + "type": "any" + }, + { + "name": "code_variables_mask_url_credentials", + "description": "Scrub credentials embedded in URLs/DSNs (e.g. ``user:pass@host``) from captured code variables, regardless of the surrounding variable name. Defaults to True.", + "isOptional": false, + "type": "any" + }, + { + "name": "code_variables_detect_secrets", + "description": "Last-resort entropy-based detection that redacts high-entropy secret-looking values (API keys, tokens, strong passwords) sitting in innocuously-named variables, after the name and URL checks. Skips structured ids (UUIDs, ObjectIds, hashes). Defaults to True.", + "isOptional": false, + "type": "any" + }, + { + "name": "in_app_modules", + "description": "Module/package prefixes treated as in-app frames in captured exceptions.", + "isOptional": false, + "type": "UnionType[list[str], any]" + }, + { + "name": "enable_exception_autocapture_rate_limiting", + "description": "Rate limit autocaptured exceptions client-side with a token bucket per exception type. Disabled by default.", + "isOptional": false, + "type": "bool" + }, + { + "name": "exception_autocapture_bucket_size", + "description": "Maximum burst of autocaptured exceptions allowed per exception type (token bucket size, clamped to 0-100).", + "isOptional": false, + "type": "int" + }, + { + "name": "exception_autocapture_refill_rate", + "description": "Tokens restored per refill interval for each exception type's bucket.", + "isOptional": false, + "type": "int" + }, + { + "name": "exception_autocapture_refill_interval_seconds", + "description": "Seconds between token refills for autocaptured exception rate limiting.", + "isOptional": false, + "type": "int" + }, + { + "name": "capture_mode", + "description": "Capture wire protocol to use. Defaults to ``CaptureMode.V0`` (legacy ``/batch/``). Set ``CaptureMode.V1`` (or pass the string ``\"v1\"``) to opt into ``/i/v1/analytics/events``. When omitted, the ``POSTHOG_CAPTURE_MODE`` env var is consulted, then ``V0``.", + "isOptional": false, + "type": "CaptureMode" + }, + { + "name": "capture_compression", + "description": "Request-body compression for capture-v1 uploads (ignored in V0, which uses ``gzip``). ``CaptureCompression.GZIP`` or ``DEFLATE`` (or the strings ``\"gzip\"``/``\"deflate\"``). When omitted, the ``POSTHOG_CAPTURE_COMPRESSION`` env var is consulted, then the legacy ``gzip`` flag, then no compression.", + "isOptional": false, + "type": "CaptureCompression" + }, + { + "name": "secret_key", + "description": "A Personal API Key or Project Secret API Key, used to authenticate local feature flag evaluation, remote config payloads, and decrypted flag payloads. Example:: posthog.Client(project_api_key, secret_key=\"phx_...\")", + "isOptional": false, + "type": "any" + }, + { + "name": "metrics", + "description": "", + "isOptional": true, + "type": "dict" + }, + { + "name": "enable_full_ai_capture", + "description": "Route PostHog AI wrapper events through the dedicated AI capture endpoint and capture full AI content: skips string truncation and passes media (base64/data URIs) through unredacted. ``privacy_mode`` always wins. Defaults to False.", + "isOptional": false, + "type": "bool" + }, + { + "name": "capture_trace_context", + "description": "When OpenTelemetry is installed and a valid span is active at capture time, add its trace and span IDs as ``$trace_id`` and ``$span_id`` properties to events captured with ``capture()`` and ``capture_ai()``, so they can be correlated with backend traces. Explicit ``$trace_id``/``$span_id`` values passed in ``properties`` win. Exception events (``capture_exception``) always attach these IDs regardless of this setting. Defaults to False.", + "isOptional": false, + "type": "bool" + }, + { + "name": "_use_ai_lane", + "description": "", + "isOptional": false, + "type": "bool" + }, + { + "name": "_enable_multimodal_capture", + "description": "", + "isOptional": false, + "type": "bool" + }, + { + "name": "traces", + "description": "Config dict for distributed tracing: ``service_name``, ``service_version``, ``environment``, ``resource_attributes``, ``flush_interval`` (5 s), ``max_queue_size`` (2048), ``max_export_batch_size`` (512), ``max_live_spans`` (10000), ``max_span_age`` (3600 s), ``max_attributes_per_span`` (128), ``max_events_per_span`` (128), ``max_attribute_value_length`` (8192). ``before_span_send`` is a callable, or a list run in order, that receives each finished span as a dict (``trace_id``, ``span_id`` and ``parent_span_id`` are read-only) and returns it, edited, or ``None`` to drop it; a hook that raises drops the span. Tracing is off until this is provided. Spans export on a background timer even with ``sync_mode``; serverless handlers should call ``flush()`` before returning. Defaults to None.", + "isOptional": true, + "type": "dict" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import Posthog\n\nposthog = Posthog('', host='')" + } + ] + }, + { + "id": "alias", + "title": "alias", + "description": "Create an alias between two distinct IDs.", + "details": "", + "category": "Identification", + "params": [ + { + "name": "previous_id", + "description": "The previous distinct ID. Required - the call is dropped with a warning if it is missing or empty.", + "isOptional": true, + "type": "Number" + }, + { + "name": "distinct_id", + "description": "The new distinct ID to alias to. Falls back to the context distinct ID; the call is dropped with a warning if neither is available.", + "isOptional": true, + "type": "str" + }, + { + "name": "timestamp", + "description": "The timestamp of the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC.", + "isOptional": false, + "type": "datetime" + }, + { + "name": "uuid", + "description": "A unique identifier for the event. If provided, it must be a valid UUID string or uuid.UUID instance; invalid values are ignored and replaced with a newly generated UUID.", + "isOptional": true, + "type": "str" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this event.", + "isOptional": true, + "type": "bool" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.alias(previous_id='distinct_id', distinct_id='alias_id')" + } + ] + }, + { + "id": "capture", + "title": "capture", + "description": "Captures an event manually. [Learn about capture best practices](https://posthog.com/docs/product-analytics/capture-events)", + "details": "", + "category": "Capture", + "params": [ + { + "name": "event", + "description": "The event name to capture.", + "isOptional": true, + "type": "str" + }, + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalCaptureArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Anonymous event", + "code": "# Anonymous event\nposthog.capture('some-anon-event')" + }, + { + "id": "example_2", + "name": "Context usage", + "code": "# Context usage\nfrom posthog import identify_context, new_context\nwith new_context():\n identify_context('distinct_id_of_the_user')\n posthog.capture('user_signed_up')\n posthog.capture('user_logged_in')\n posthog.capture('some-custom-action', distinct_id='distinct_id_of_the_user')" + }, + { + "id": "example_3", + "name": "Set event properties", + "code": "# Set event properties\nposthog.capture(\n \"user_signed_up\",\n distinct_id=\"distinct_id_of_the_user\",\n properties={\n \"login_type\": \"email\",\n \"is_free_trial\": \"true\"\n }\n)" + }, + { + "id": "example_4", + "name": "Page view event", + "code": "# Page view event\nposthog.capture('$pageview', distinct_id=\"distinct_id_of_the_user\", properties={'$current_url': 'https://example.com'})" + } + ] + }, + { + "id": "capture_ai", + "title": "capture_ai", + "description": "Capture an AI event on the dedicated AI capture endpoint. Beta: the signature is stable; operational limits (per-event size cap, batching, endpoint) may change without notice. Takes the same arguments and returns the same value as `capture()`: the event UUID, or None when the event was not admitted (disabled client, or dropped by `before_send`). The event is queued on an isolated AI lane with its own consumer pool and a higher per-event size cap, posting to the dedicated AI ingestion endpoint. The payload is sent as given \u2014 no redaction or truncation is applied here.", + "details": "", + "category": "Capture", + "params": [ + { + "name": "event", + "description": "", + "isOptional": true, + "type": "str" + }, + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalCaptureArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + } + }, + { + "id": "capture_exception", + "title": "capture_exception", + "description": "Capture an exception for error tracking. When OpenTelemetry is installed and a valid span is active, its trace and span IDs are added as ``$trace_id`` and ``$span_id`` event properties.", + "details": "", + "category": "Error Tracking", + "params": [ + { + "name": "exception", + "description": "The exception to capture.", + "isOptional": true, + "type": "BaseException" + }, + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalCaptureArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "try:\n # Some code that might fail\n pass\nexcept Exception as e:\n posthog.capture_exception(e, 'user_distinct_id', properties=additional_properties)" + } + ] + }, + { + "id": "evaluate_flags", + "title": "evaluate_flags", + "description": "Evaluate all feature flags for a user in a single call and return a :class:`FeatureFlagEvaluations` snapshot. Branch on ``.is_enabled()`` / ``.get_flag()`` and pass the same snapshot to :meth:`capture` via the ``flags`` option so events carry the exact flag values the code branched on. Prefer this over repeated ``get_feature_flag()`` calls and over ``capture(send_feature_flags=True)`` \u2014 it consolidates flag evaluation into a single ``/flags`` request per incoming request. Local evaluation is transparent: when the poller resolves a flag, the snapshot's ``$feature_flag_called`` events are tagged ``locally_evaluated=True`` and reason ``\"Evaluated locally\"``.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The user's distinct ID. If ``None``, falls back to the context distinct_id. If still unresolvable, returns an empty snapshot.", + "isOptional": false, + "type": "Number" + }, + { + "name": "groups", + "description": "Mapping of group type to group key.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "Person properties to use for evaluation.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "Group properties keyed by group type.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "If True, never fall back to remote evaluation \u2014 flags that can't be evaluated locally are simply omitted from the snapshot.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup.", + "isOptional": true, + "type": "bool" + }, + { + "name": "flag_keys", + "description": "Optional list that scopes local evaluation, the underlying ``/flags`` request, and the returned snapshot. When omitted or ``None``, all flags are evaluated. An empty list returns an empty snapshot without evaluating flags. A requested key absent from loaded local definitions is included in one remote fallback per ``evaluate_flags`` call unless ``only_evaluate_locally`` is True. If the server also does not know the key, it is omitted from the snapshot.", + "isOptional": true, + "type": "list[str]" + }, + { + "name": "device_id", + "description": "Optional device ID override. If not provided, falls back to the context device_id (which may be set via tracing headers). Used by experience-continuity flags to match users across distinct_id changes.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "FeatureFlagEvaluations" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "flags = posthog.evaluate_flags(\n \"user_123\",\n person_properties={\"plan\": \"enterprise\"},\n)\nif flags.is_enabled(\"new-dashboard\"):\n render_new_dashboard()\nposthog.capture(\"page_viewed\", distinct_id=\"user_123\", flags=flags)" + } + ] + }, + { + "id": "feature_enabled", + "title": "feature_enabled", + "description": "Check if a feature flag is enabled for a user.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "The feature flag key.", + "isOptional": true, + "type": "str" + }, + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to only evaluate locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "send_feature_flag_events", + "description": "Whether to send feature flag events.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[bool]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "is_my_flag_enabled = posthog.feature_enabled('flag-key', 'distinct_id_of_your_user')\nif is_my_flag_enabled:\n # Do something differently for this user\n # Optional: fetch the payload\n matched_flag_payload = posthog.get_feature_flag_payload('flag-key', 'distinct_id_of_your_user')" + } + ] + }, + { + "id": "feature_flag_definitions", + "title": "feature_flag_definitions", + "description": "Return feature flag definitions loaded for local evaluation. Returns: The currently loaded feature flag definitions, or ``None`` before local evaluation has loaded definitions.", + "details": "", + "category": "Feature flags", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "flush", + "title": "flush", + "description": "Attempt immediate delivery of queued events and ended spans, keeping the client reusable. For a client shared across requests or serverless invocations (including the module-level client), call ``flush()`` before a short-lived invocation returns. Use ``shutdown()`` instead only for final process cleanup or when disposing of a client that will not be reused. Flush does not stop the background workers or feature flag poller, and does not end open spans. Returning does not guarantee server receipt: pending work can remain after a timeout or span export failure, and failed events may be dropped by the consumers. Calls made directly from SDK callbacks such as ``on_error`` are deferred rather than blocking the worker that invoked the callback.", + "details": "", + "category": null, + "params": [ + { + "name": "timeout_seconds", + "description": "Seconds to wait for event queues to drain, shared across event lanes. Defaults to 10 seconds. Pass ``None`` to wait without a time limit; this does not guarantee successful delivery. Queued spans flush concurrently. The span exporter waits up to this many seconds for an in-flight flush, then uses a fresh drain budget once it acquires the lock. If the lock wait times out, the in-flight flush is left to finish. At least one span request is attempted after acquiring the lock, even with no budget left; retriable failures are retried after backoff while budget remains, with one last attempt at the deadline. Each span request is bounded by the client's ``timeout`` in seconds, and the last request is not cut short. This is therefore not a strict wall-clock limit for the whole call. With ``None``, a retriable span failure is left for a later flush rather than retried within this call.", + "isOptional": true, + "type": "float" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.capture('event_name', distinct_id='user_id')\n# Before a serverless invocation returns; keep the shared client open.\nposthog.flush(timeout_seconds=10)" + } + ] + }, + { + "id": "get_active_span", + "title": "get_active_span", + "description": "The span that is active in the current context, or ``None``. Alpha. Only entering a span (``with posthog.start_span(...) as span:``) makes it active; a span started manually is not. Use it to propagate the trace to the next service: ``span.traceparent()`` is the header value.", + "details": "", + "category": "Tracing", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[Span]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "span = posthog.get_active_span()\nif span is not None:\n headers[\"traceparent\"] = span.traceparent()" + } + ] + }, + { + "id": "get_all_flags", + "title": "get_all_flags", + "description": "Get all feature flags for a user.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to only evaluate locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "flag_keys_to_evaluate", + "description": "A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance.", + "isOptional": true, + "type": "list[str]" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[dict[str, Union[bool, str]]]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.get_all_flags('distinct_id_of_your_user')" + } + ] + }, + { + "id": "get_all_flags_and_payloads", + "title": "get_all_flags_and_payloads", + "description": "Get all feature flags and their payloads for a user.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to only evaluate locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "flag_keys_to_evaluate", + "description": "A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance.", + "isOptional": true, + "type": "list[str]" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "FlagsAndPayloads" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.get_all_flags_and_payloads('distinct_id_of_your_user')" + } + ] + }, + { + "id": "get_feature_flag", + "title": "get_feature_flag", + "description": "Get multivariate feature flag value for a user.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "The feature flag key.", + "isOptional": true, + "type": "str" + }, + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to only evaluate locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "send_feature_flag_events", + "description": "Whether to send feature flag events.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Union[bool, str, any]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "enabled_variant = posthog.get_feature_flag('flag-key', 'distinct_id_of_your_user')\nif enabled_variant == 'variant-key': # replace 'variant-key' with the key of your variant\n # Do something differently for this user\n # Optional: fetch the payload\n matched_flag_payload = posthog.get_feature_flag_payload('flag-key', 'distinct_id_of_your_user')" + } + ] + }, + { + "id": "get_feature_flag_evaluation_runtime", + "title": "get_feature_flag_evaluation_runtime", + "description": "Return where a locally loaded feature flag is meant to be evaluated.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "The feature flag key.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[FeatureFlagEvaluationRuntime]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import FeatureFlagEvaluationRuntime\n\nruntime = posthog.get_feature_flag_evaluation_runtime(\"my-flag\")\nif runtime is FeatureFlagEvaluationRuntime.SERVER:\n ..." + } + ] + }, + { + "id": "get_feature_flag_keys_by_evaluation_runtime", + "title": "get_feature_flag_keys_by_evaluation_runtime", + "description": "Return the keys of locally loaded flags that a runtime can evaluate. A flag set to ``FeatureFlagEvaluationRuntime.ALL`` suits either runtime, so it is returned for ``CLIENT`` and for ``SERVER``, and asking for ``ALL`` returns every loaded flag. Use this to decide which flags to hand to a browser when a backend serves flags to its own frontend.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "evaluation_runtime", + "description": "The runtime to match, as a ``FeatureFlagEvaluationRuntime`` or its string value.", + "isOptional": true, + "type": "FeatureFlagEvaluationRuntime" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "list[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import FeatureFlagEvaluationRuntime\n\nclient_keys = posthog.get_feature_flag_keys_by_evaluation_runtime(\n FeatureFlagEvaluationRuntime.CLIENT\n)" + } + ] + }, + { + "id": "get_feature_flag_payload", + "title": "get_feature_flag_payload", + "description": "Get the payload for a feature flag.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "The feature flag key.", + "isOptional": true, + "type": "str" + }, + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "match_value", + "description": "The specific flag value to get payload for.", + "isOptional": false, + "type": "bool" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to only evaluate locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "send_feature_flag_events", + "description": "Deprecated. Use get_feature_flag() instead if you need events.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[object]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "is_my_flag_enabled = posthog.feature_enabled('flag-key', 'distinct_id_of_your_user')\n\nif is_my_flag_enabled:\n # Do something differently for this user\n # Optional: fetch the payload\n matched_flag_payload = posthog.get_feature_flag_payload('flag-key', 'distinct_id_of_your_user')" + } + ] + }, + { + "id": "get_feature_flag_result", + "title": "get_feature_flag_result", + "description": "Get a FeatureFlagResult object which contains the flag result and payload for a key by evaluating locally or remotely depending on whether local evaluation is enabled and the flag can be locally evaluated. This also captures the `$feature_flag_called` event unless `send_feature_flag_events` is `False`.", + "details": "", + "category": null, + "params": [ + { + "name": "key", + "description": "The feature flag key.", + "isOptional": true, + "type": "str" + }, + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to only evaluate locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "send_feature_flag_events", + "description": "Whether to send feature flag events.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[FeatureFlagResult]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "flag_result = posthog.get_feature_flag_result('flag-key', 'distinct_id_of_your_user')\nif flag_result and flag_result.get_value() == 'variant-key':\n # Do something differently for this user\n # Optional: fetch the payload\n matched_flag_payload = flag_result.payload" + } + ] + }, + { + "id": "get_feature_flags_and_payloads", + "title": "get_feature_flags_and_payloads", + "description": "Get feature flags and payloads for a user.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "flag_keys_to_evaluate", + "description": "A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance.", + "isOptional": true, + "type": "list[str]" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "FlagsAndPayloads" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "result = posthog.get_feature_flags_and_payloads('')" + } + ] + }, + { + "id": "get_feature_payloads", + "title": "get_feature_payloads", + "description": "Get feature flag payloads for a user, preserving valid serialized JSON.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "flag_keys_to_evaluate", + "description": "A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance.", + "isOptional": true, + "type": "list[str]" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "dict[str, Optional[str]]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "payloads = posthog.get_feature_payloads('')" + } + ] + }, + { + "id": "get_feature_variants", + "title": "get_feature_variants", + "description": "Get feature flag variants for a user.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "flag_keys_to_evaluate", + "description": "A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance.", + "isOptional": true, + "type": "list[str]" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "dict[str, Union[bool, str]]" + } + }, + { + "id": "get_flags_decision", + "title": "get_flags_decision", + "description": "Get feature flags decision.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": false, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "flag_keys_to_evaluate", + "description": "A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance.", + "isOptional": true, + "type": "list[str]" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "FlagsResponse" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "decision = posthog.get_flags_decision('user123')" + } + ] + }, + { + "id": "get_remote_config_payload", + "title": "get_remote_config_payload", + "description": "Get the payload for a remote config feature flag.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "The remote config feature flag key.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "get_tags", + "title": "get_tags", + "description": "Get all tags from the current context. Returns: Dict of all tags in the current context.", + "details": "", + "category": "Contexts", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "dict[str, Any]" + } + }, + { + "id": "group_identify", + "title": "group_identify", + "description": "Identify a group and set its properties.", + "details": "", + "category": "Identification", + "params": [ + { + "name": "group_type", + "description": "The type of group (e.g., 'company', 'team'). Required - the call is dropped with a warning if it is missing or empty.", + "isOptional": true, + "type": "str" + }, + { + "name": "group_key", + "description": "The unique identifier for the group. Required - the call is dropped with a warning if it is missing or empty.", + "isOptional": true, + "type": "str" + }, + { + "name": "properties", + "description": "A dictionary of properties to set on the group.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "timestamp", + "description": "The timestamp of the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC.", + "isOptional": false, + "type": "datetime" + }, + { + "name": "uuid", + "description": "A unique identifier for the event. If provided, it must be a valid UUID string or uuid.UUID instance; invalid values are ignored and replaced with a newly generated UUID.", + "isOptional": false, + "type": "str" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this event.", + "isOptional": true, + "type": "bool" + }, + { + "name": "distinct_id", + "description": "The distinct ID of the user performing the action.", + "isOptional": false, + "type": "Number" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.group_identify('company', 'company_id_in_your_db', {\n 'name': 'Awesome Inc.',\n 'employees': 11\n})" + } + ] + }, + { + "id": "identify_context", + "title": "identify_context", + "description": "Identify the current context with a distinct ID.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "distinct_id", + "description": "The distinct ID to associate with the current context and its children.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + } + }, + { + "id": "join", + "title": "join", + "description": "Attempt to process queued events and end the consumer threads. Do not use directly, call `shutdown()` instead. Failed or undrainable events may be dropped and reported through logging or ``on_error``; returning does not guarantee server receipt. Lifecycle cleanup is attempted once, and cleanup failures are logged without retry.", + "details": "", + "category": null, + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.join()" + } + ] + }, + { + "id": "load_feature_flags", + "title": "load_feature_flags", + "description": "Load feature flags for local evaluation.", + "details": "", + "category": "Feature flags", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.load_feature_flags()" + } + ] + }, + { + "id": "new_context", + "title": "new_context", + "description": "Create a new context for managing shared state. Learn more about [contexts](/docs/libraries/python#contexts).", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "fresh", + "description": "Whether to create a fresh context that doesn't inherit from parent.", + "isOptional": false, + "type": "bool" + }, + { + "name": "capture_exceptions", + "description": "Whether to automatically capture exceptions in this context. If omitted, defaults to this client's exception autocapture setting.", + "isOptional": true, + "type": "bool" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "with client.new_context():\n client.identify_context('')\n client.capture('event_name')" + } + ] + }, + { + "id": "scoped", + "title": "scoped", + "description": "Decorator that creates a new context for the wrapped function using this client.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "fresh", + "description": "Whether to create a fresh context that doesn't inherit from parent.", + "isOptional": false, + "type": "bool" + }, + { + "name": "capture_exceptions", + "description": "Whether to automatically capture exceptions in this context. If omitted, defaults to this client's exception autocapture setting.", + "isOptional": true, + "type": "bool" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "set", + "title": "set", + "description": "Set properties on a person profile.", + "details": "", + "category": "Identification", + "params": [ + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalSetArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Set with distinct id", + "code": "# Set with distinct id\nposthog.set(distinct_id='user123', properties={'name': 'Max Hedgehog'})" + } + ] + }, + { + "id": "set_context_device_id", + "title": "set_context_device_id", + "description": "Set the device ID for the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "device_id", + "description": "The device ID to associate with the current context and its children.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + } + }, + { + "id": "set_context_session", + "title": "set_context_session", + "description": "Set the session ID for the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "session_id", + "description": "The session ID to associate with the current context and its children.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + } + }, + { + "id": "set_once", + "title": "set_once", + "description": "Set properties on a person profile only if they haven't been set before.", + "details": "", + "category": "Identification", + "params": [ + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalSetArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.set_once(distinct_id='user123', properties={'initial_signup_date': '2024-01-01'})" + } + ] + }, + { + "id": "shutdown", + "title": "shutdown", + "description": "Attempt final delivery and permanently shut down the client. Call this for final process cleanup or when disposing of a client that will not be reused. It closes capture lanes, stops background workers and the feature flag poller, and tears down integrations. Do not call it at the end of each request or serverless invocation when the client is shared; call ``flush()`` before returning instead. A shut-down client cannot be reused; create a new client if needed. Normally this method blocks until queued events have been attempted and cleanup finishes. Failed or undrainable events may be dropped and reported through logging or ``on_error``; returning does not guarantee server receipt. Event draining has no timeout. Queued spans get one final flush with up to 30 seconds waiting for an in-flight flush, then a fresh 30-second drain budget; the last HTTP request is not cut short. Any spans it cannot send are discarded with a warning, as are spans still open. Lifecycle cleanup is attempted once, and cleanup failures are logged without retry. When called directly from an SDK callback such as ``on_error``, shutdown is deferred to avoid blocking the worker that invoked the callback. If the callback must coordinate a blocking shutdown, have it signal an application-owned thread and return before that thread calls shutdown. Do not wait inside the callback for another thread or task that calls a lifecycle method.", + "details": "", + "category": null, + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.shutdown()" + } + ] + }, + { + "id": "start_span", + "title": "start_span", + "description": "Start a span for distributed tracing. Alpha. Returns a span handle. Use it as a context manager to make it the active span for the block and end it on exit (recording a raised exception on the way out); or call ``end()`` yourself for a span that cannot wrap a block. Spans started inside the block nest under it automatically. Always returns a usable handle, even when tracing is off, so calling code never branches.", + "details": "", + "category": "Tracing", + "params": [ + { + "name": "name", + "description": "A low-cardinality operation name, e.g. ``GET /users/:id``. Variable values belong in attributes, not the name.", + "isOptional": true, + "type": "str" + }, + { + "name": "kind", + "description": "``internal`` (default), ``server``, ``client``, ``producer`` or ``consumer``.", + "isOptional": true, + "type": "str" + }, + { + "name": "attributes", + "description": "Initial attributes.", + "isOptional": true, + "type": "Mapping[str, Any]" + }, + { + "name": "parent", + "description": "A span handle, or an inbound W3C ``traceparent`` header value to continue a remote trace. Defaults to the active span. A forked child starts with no active span; pass the parent span to continue a trace across a fork.", + "isOptional": false, + "type": "Span" + }, + { + "name": "tracestate", + "description": "The inbound ``tracestate`` header accompanying a ``traceparent`` string ``parent``; preserved and propagated.", + "isOptional": true, + "type": "str" + }, + { + "name": "start_time", + "description": "A ``datetime`` or epoch seconds, to backdate the span.", + "isOptional": false, + "type": "datetime" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Span" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog = Posthog(\"\", traces={\"service_name\": \"checkout-api\"})\n\nwith posthog.start_span(\"POST /checkout\", parent=request.headers.get(\"traceparent\")) as span:\n span.set_attribute(\"plan\", user.plan)\n with posthog.start_span(\"db.query\", kind=\"client\"):\n ...\n outgoing_headers = {\"traceparent\": span.traceparent()}" + } + ] + }, + { + "id": "tag", + "title": "tag", + "description": "Add a tag to the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "name", + "description": "The tag key.", + "isOptional": true, + "type": "str" + }, + { + "name": "value", + "description": "The tag value.", + "isOptional": true, + "type": "Any" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + } + } + ] + }, + { + "id": "PostHogModule", + "title": "PostHog Module Functions", + "description": "Global functions available in the PostHog module", + "functions": [ + { + "id": "alias", + "title": "alias", + "description": "Associate user behaviour before and after they e.g. register, login, or perform some other identifying action.", + "details": "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?\". Particularly useful for associating user behaviour before and after they e.g. register, login, or perform some other identifying action.", + "category": "Identification", + "params": [ + { + "name": "previous_id", + "description": "The unique ID of the user before", + "isOptional": true, + "type": "Number" + }, + { + "name": "distinct_id", + "description": "The current unique id", + "isOptional": true, + "type": "str" + }, + { + "name": "timestamp", + "description": "Optional timestamp for the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC.", + "isOptional": false, + "type": "datetime" + }, + { + "name": "uuid", + "description": "Optional UUID for the event", + "isOptional": true, + "type": "str" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup", + "isOptional": true, + "type": "bool" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Alias user", + "code": "# Alias user\nfrom posthog import alias\nalias(previous_id='distinct_id', distinct_id='alias_id')" + } + ] + }, + { + "id": "capture", + "title": "capture", + "description": "Capture anything a user does within your system.", + "details": "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. A capture call requires an event name to specify the event. We recommend using [verb] [noun], like `movie played` or `movie updated` to easily identify what your events mean later on. Capture takes a number of optional arguments, which are defined by the `OptionalCaptureArgs` type.", + "category": "Events", + "params": [ + { + "name": "event", + "description": "The event name to specify the event **kwargs: Optional arguments including:", + "isOptional": true, + "type": "str" + }, + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalCaptureArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Context and capture usage", + "code": "# Context and capture usage\nfrom posthog import new_context, identify_context, tag_context, capture\n# Enter a new context (e.g. a request/response cycle, an instance of a background job, etc)\nwith new_context():\n # Associate this context with some user, by distinct_id\n identify_context('some user')\n\n # Capture an event, associated with the context-level distinct ID ('some user')\n capture('movie started')\n\n # Capture an event associated with some other user (overriding the context-level distinct ID)\n capture('movie joined', distinct_id='some-other-user')\n\n # Capture an event with some properties\n capture('movie played', properties={'movie_id': '123', 'category': 'romcom'})\n\n # Capture an event with some properties\n capture('purchase', properties={'product_id': '123', 'category': 'romcom'})\n # Capture an event with some associated group\n capture('purchase', groups={'company': 'id:5'})\n\n # Adding a tag to the current context will cause it to appear on all subsequent events\n tag_context('some-tag', 'some-value')\n\n capture('another-event') # Will be captured with `'some-tag': 'some-value'` in the properties dict" + }, + { + "id": "example_2", + "name": "Set event properties", + "code": "# Set event properties\nfrom posthog import capture\ncapture(\n \"user_signed_up\",\n distinct_id=\"distinct_id_of_the_user\",\n properties={\n \"login_type\": \"email\",\n \"is_free_trial\": \"true\"\n }\n)" + } + ] + }, + { + "id": "capture_ai", + "title": "capture_ai", + "description": "Capture an AI event on the dedicated AI capture endpoint. Beta: the signature is stable; operational limits (per-event size cap, batching, endpoint) may change without notice. Takes the same arguments and returns the same value as `capture()`: the event UUID, or None when the event was not admitted (disabled client, or dropped by `before_send`). The event is delivered on an isolated queue with its own consumer pool and a higher per-event size cap, posting to the dedicated AI ingestion endpoint. The payload is sent as given \u2014 no redaction or truncation is applied here.", + "details": "", + "category": "Events", + "params": [ + { + "name": "event", + "description": "The event name, normally one of the `$ai_*` event names. **kwargs: Same optional arguments as `capture()`.", + "isOptional": true, + "type": "str" + }, + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalCaptureArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import capture_ai\n\nuuid = capture_ai(\n \"$ai_generation\",\n distinct_id=\"user_123\",\n properties={\"$ai_model\": \"gpt-5\"},\n)" + } + ] + }, + { + "id": "capture_exception", + "title": "capture_exception", + "description": "Capture exceptions that happen in your code.", + "details": "Capture exception is idempotent - if it is called twice with the same exception instance, only a occurrence will be tracked in posthog. This is because, generally, contexts will cause exceptions to be captured automatically. However, to ensure you track an exception, if you catch and do not re-raise it, capturing it manually is recommended, unless you are certain it will have crossed a context boundary (e.g. by existing a `with posthog.new_context():` block already). If the passed exception was raised and caught, the captured stack trace will consist of every frame between where the exception was raised and the point at which it is captured (the \"traceback\"). If the passed exception was never raised, e.g. if you call `posthog.capture_exception(ValueError(\"Some Error\"))`, the stack trace captured will be the full stack trace at the moment the exception was captured. Note that heavy use of contexts will lead to truncated stack traces, as the exception will be captured by the context entered most recently, 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. `capture_exception` takes the same set of optional arguments as `capture`.", + "category": "Events", + "params": [ + { + "name": "exception", + "description": "The exception to capture. If not provided, the current exception is captured via `sys.exc_info()` **kwargs: Optional capture arguments including distinct_id, properties, timestamp, uuid, groups, flags, send_feature_flags, and disable_geoip. Overriding reserved exception properties through ``properties`` is deprecated and will stop working in the next major version.", + "isOptional": false, + "type": "BaseException" + }, + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalCaptureArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Capture exception", + "code": "# Capture exception\nfrom posthog import capture_exception\ntry:\n risky_operation()\nexcept Exception as e:\n capture_exception(e)" + } + ] + }, + { + "id": "evaluate_flags", + "title": "evaluate_flags", + "description": "Evaluate all feature flags for a user in a single call and return a :class:`FeatureFlagEvaluations` snapshot. Branch on ``.is_enabled()`` / ``.get_flag()`` and pass the same snapshot to ``capture()`` via the ``flags`` option so events carry the exact flag values the code branched on. Prefer this over repeated ``get_feature_flag()`` calls and over ``capture(send_feature_flags=True)`` \u2014 it consolidates flag evaluation into a single ``/flags`` request per incoming request.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The user's distinct ID. If ``None``, falls back to the context distinct_id. If still unresolvable, returns an empty snapshot.", + "isOptional": false, + "type": "Number" + }, + { + "name": "groups", + "description": "Mapping of group type to group key.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "Person properties to use for evaluation.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "Group properties keyed by group type.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "If ``True``, never fall back to remote evaluation and omit flags that cannot be evaluated locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup.", + "isOptional": true, + "type": "bool" + }, + { + "name": "flag_keys", + "description": "Optional list that scopes local evaluation, the underlying ``/flags`` request, and the returned snapshot. When omitted or ``None``, all flags are evaluated. An empty list returns an empty snapshot without evaluating flags. A requested key absent from loaded local definitions is included in one remote fallback per ``evaluate_flags`` call unless ``only_evaluate_locally`` is ``True``. If the server also does not know the key, it is omitted from the snapshot.", + "isOptional": true, + "type": "list[str]" + }, + { + "name": "device_id", + "description": "Optional device ID override. If not provided, falls back to the context device_id (which may be set via tracing headers). Used by experience-continuity flags to match users across distinct_id changes.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "FeatureFlagEvaluations" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import evaluate_flags, capture\nflags = evaluate_flags(\"user_123\", person_properties={\"plan\": \"enterprise\"})\nif flags.is_enabled(\"new-dashboard\"):\n render_new_dashboard()\ncapture(\"page_viewed\", distinct_id=\"user_123\", flags=flags)" + } + ] + }, + { + "id": "feature_enabled", + "title": "feature_enabled", + "description": "Use feature flags to enable or disable features for users.", + "details": "You can call `posthog.load_feature_flags()` before to make sure you're not doing unexpected requests.", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "The feature flag key", + "isOptional": true, + "type": "str" + }, + { + "name": "distinct_id", + "description": "The user's distinct ID", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "Groups mapping", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "Person properties", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "Group properties", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to evaluate only locally", + "isOptional": false, + "type": "bool" + }, + { + "name": "send_feature_flag_events", + "description": "Whether to send feature flag events", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "Optional device ID override for experience-continuity flags", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[bool]" + }, + "examples": [ + { + "id": "example_1", + "name": "Boolean feature flag", + "code": "# Boolean feature flag\nfrom posthog import feature_enabled, get_feature_flag_payload\nis_my_flag_enabled = feature_enabled('flag-key', 'distinct_id_of_your_user')\nif is_my_flag_enabled:\n matched_flag_payload = get_feature_flag_payload('flag-key', 'distinct_id_of_your_user')" + } + ] + }, + { + "id": "feature_flag_definitions", + "title": "feature_flag_definitions", + "description": "Returns loaded feature flags.", + "details": "Returns loaded feature flags, if any. Helpful for debugging what flag information you have loaded.", + "category": "Feature flags", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import feature_flag_definitions\ndefinitions = feature_flag_definitions()" + } + ] + }, + { + "id": "flush", + "title": "flush", + "description": "Attempt immediate delivery of queued events and ended spans, keeping the global client reusable. Call ``flush()`` before a short-lived request or serverless invocation returns when using the shared module-level client. Use ``shutdown()`` only for final process cleanup or a client that will not be reused. Flush does not stop the background workers or feature flag poller, and does not end open spans. Returning does not guarantee server receipt: pending work can remain after a timeout or span export failure, and failed events may be dropped by consumers. Calls made directly from SDK callbacks such as ``on_error`` are deferred rather than blocking the worker that invoked the callback.", + "details": "", + "category": "Client management", + "params": [ + { + "name": "timeout_seconds", + "description": "Seconds to wait for event queues to drain, shared across event lanes. Defaults to 10 seconds. Pass ``None`` to wait without a time limit; this does not guarantee successful delivery. Queued spans flush concurrently, waiting up to this many seconds for an in-flight flush before starting a fresh drain budget. A lock wait timeout leaves the in-flight flush to finish. At least one span request is attempted after acquiring the lock, even with no budget left; retriable failures are retried after backoff while budget remains, with one last attempt at the deadline. The last span request is not cut short and is bounded by the client's ``timeout`` in seconds, so this is not a strict wall-clock limit for the whole call. With ``None``, a retriable span failure is left for a later flush rather than retried within this call.", + "isOptional": true, + "type": "float" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "import posthog\nposthog.capture('event_name', distinct_id='user_id')\n# Before a serverless invocation returns; keep the global client open.\nposthog.flush(timeout_seconds=10)" + } + ] + }, + { + "id": "get_active_span", + "title": "get_active_span", + "description": "The span that is active in the current context, or ``None``. Alpha. Only entering a span (``with posthog.start_span(...) as span:``) makes it active; a span started manually is not. Use it to propagate the trace to the next service: ``span.traceparent()`` is the header value.", + "details": "", + "category": "Tracing", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[Span]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "span = posthog.get_active_span()\nif span is not None:\n headers[\"traceparent\"] = span.traceparent()" + } + ] + }, + { + "id": "get_all_flags", + "title": "get_all_flags", + "description": "Get all flags for a given user.", + "details": "Flags are key-value pairs where the key is the flag key and the value is the flag variant, or True, or False.", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The user's distinct ID", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "Groups mapping", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "Person properties", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "Group properties", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to evaluate only locally", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "Optional device ID override for experience-continuity flags", + "isOptional": true, + "type": "str" + }, + { + "name": "flag_keys_to_evaluate", + "description": "Optional list of flag keys to evaluate (evaluates all if None)", + "isOptional": true, + "type": "list[str]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[dict[str, Union[bool, str]]]" + }, + "examples": [ + { + "id": "example_1", + "name": "All flags for user", + "code": "# All flags for user\nfrom posthog import get_all_flags\nget_all_flags('distinct_id_of_your_user')" + } + ] + }, + { + "id": "get_all_flags_and_payloads", + "title": "get_all_flags_and_payloads", + "description": "Get all feature flag values and payloads for a user.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The user's distinct ID.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "Mapping of group type to group key.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "Person properties to use for evaluation.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "Group properties keyed by group type.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to evaluate only locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup.", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "Optional device ID override for experience-continuity flags.", + "isOptional": true, + "type": "str" + }, + { + "name": "flag_keys_to_evaluate", + "description": "Optional list of flag keys to evaluate. Evaluates all flags when omitted.", + "isOptional": true, + "type": "list[str]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "FlagsAndPayloads" + } + }, + { + "id": "get_feature_flag", + "title": "get_feature_flag", + "description": "Get feature flag variant for users. Used with experiments.", + "details": "`groups` are a mapping from group type to group key. So, if you have a group type of \"organization\" and a group key of \"5\", you would pass groups={\"organization\": \"5\"}. `group_properties` take the format: { group_type_name: { group_properties } }. So, for example, if you have the group type \"organization\" and the group key \"5\", with the properties name, and employee count, you'll send these as: group_properties={\"organization\": {\"name\": \"PostHog\", \"employees\": 11}}.", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "The feature flag key", + "isOptional": true, + "type": "str" + }, + { + "name": "distinct_id", + "description": "The user's distinct ID", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "Groups mapping from group type to group key", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "Person properties", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "Group properties in format { group_type_name: { group_properties } }", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to evaluate only locally", + "isOptional": false, + "type": "bool" + }, + { + "name": "send_feature_flag_events", + "description": "Whether to send feature flag events", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "Optional device ID override for experience-continuity flags", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Union[bool, str, any]" + }, + "examples": [ + { + "id": "example_1", + "name": "Multivariate feature flag", + "code": "# Multivariate feature flag\nfrom posthog import get_feature_flag, get_feature_flag_payload\nenabled_variant = get_feature_flag('flag-key', 'distinct_id_of_your_user')\nif enabled_variant == 'variant-key':\n matched_flag_payload = get_feature_flag_payload('flag-key', 'distinct_id_of_your_user')" + } + ] + }, + { + "id": "get_feature_flag_evaluation_runtime", + "title": "get_feature_flag_evaluation_runtime", + "description": "Return where a locally loaded feature flag is meant to be evaluated.", + "details": "Reads the `evaluation_runtime` each flag definition carries, so no extra request is made. Returns `None` when local evaluation has not loaded a definition for this key. A definition that carries no runtime reports `FeatureFlagEvaluationRuntime.ALL`, the default PostHog applies.", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[FeatureFlagEvaluationRuntime]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import FeatureFlagEvaluationRuntime, get_feature_flag_evaluation_runtime\nruntime = get_feature_flag_evaluation_runtime(\"my-flag\")" + } + ] + }, + { + "id": "get_feature_flag_keys_by_evaluation_runtime", + "title": "get_feature_flag_keys_by_evaluation_runtime", + "description": "Return the keys of locally loaded flags that a runtime can evaluate.", + "details": "A flag set to `FeatureFlagEvaluationRuntime.ALL` suits either runtime, so it is returned for `CLIENT` and for `SERVER`. Use this to decide which flags to hand to a browser when a backend serves flags to its own frontend.", + "category": "Feature flags", + "params": [ + { + "name": "evaluation_runtime", + "description": "", + "isOptional": true, + "type": "FeatureFlagEvaluationRuntime" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "list[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import FeatureFlagEvaluationRuntime, get_feature_flag_keys_by_evaluation_runtime\nclient_keys = get_feature_flag_keys_by_evaluation_runtime(FeatureFlagEvaluationRuntime.CLIENT)" + } + ] + }, + { + "id": "get_feature_flag_payload", + "title": "get_feature_flag_payload", + "description": "Get the payload associated with a feature flag value. Deprecated for new code. Prefer ``evaluate_flags()`` and ``flags.get_flag_payload(key)`` so flag evaluation happens once per request.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "The feature flag key.", + "isOptional": true, + "type": "str" + }, + { + "name": "distinct_id", + "description": "The user's distinct ID.", + "isOptional": true, + "type": "Number" + }, + { + "name": "match_value", + "description": "Optional flag value to use when selecting a payload.", + "isOptional": false, + "type": "bool" + }, + { + "name": "groups", + "description": "Mapping of group type to group key.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "Person properties to use for evaluation.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "Group properties keyed by group type.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to evaluate only locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "send_feature_flag_events", + "description": "Whether to send a $feature_flag_called event.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup.", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "Optional device ID override for experience-continuity flags.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[object]" + } + }, + { + "id": "get_feature_flag_result", + "title": "get_feature_flag_result", + "description": "Get a FeatureFlagResult object which contains the flag result and payload. This method evaluates a feature flag and returns a FeatureFlagResult object containing: - enabled: Whether the flag is enabled - variant: The variant value if the flag has variants - payload: The payload associated with the flag (automatically deserialized from JSON) - key: The flag key - reason: Why the flag was enabled/disabled", + "details": "", + "category": null, + "params": [ + { + "name": "key", + "description": "The feature flag key.", + "isOptional": true, + "type": "str" + }, + { + "name": "distinct_id", + "description": "The user's distinct ID.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "Mapping of group type to group key.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "Person properties to use for evaluation.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "Group properties keyed by group type.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to evaluate only locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "send_feature_flag_events", + "description": "Whether to send a $feature_flag_called event.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup.", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "Optional device ID override for experience-continuity flags.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[FeatureFlagResult]" + } + }, + { + "id": "get_remote_config_payload", + "title": "get_remote_config_payload", + "description": "Get the payload for a remote config feature flag.", + "details": "", + "category": null, + "params": [ + { + "name": "key", + "description": "The key of the feature flag", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "get_tags", + "title": "get_tags", + "description": "Get all tags from the current context. Returns: Dict of all tags in the current context", + "details": "", + "category": "Contexts", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "dict[str, Any]" + } + }, + { + "id": "group_identify", + "title": "group_identify", + "description": "Set properties on a group.", + "details": "", + "category": "Identification", + "params": [ + { + "name": "group_type", + "description": "Type of your group. Required - the call is dropped with a warning if it is missing or empty.", + "isOptional": true, + "type": "str" + }, + { + "name": "group_key", + "description": "Unique identifier of the group. Required - the call is dropped with a warning if it is missing or empty.", + "isOptional": true, + "type": "str" + }, + { + "name": "properties", + "description": "Properties to set on the group", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "timestamp", + "description": "Optional timestamp for the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC.", + "isOptional": false, + "type": "datetime" + }, + { + "name": "uuid", + "description": "Optional UUID for the event", + "isOptional": true, + "type": "str" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup", + "isOptional": true, + "type": "bool" + }, + { + "name": "distinct_id", + "description": "Optional distinct ID of the user performing the action", + "isOptional": false, + "type": "Number" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Group identify", + "code": "# Group identify\nfrom posthog import group_identify\ngroup_identify('company', 'company_id_in_your_db', {\n 'name': 'Awesome Inc.',\n 'employees': 11\n})" + } + ] + }, + { + "id": "identify_context", + "title": "identify_context", + "description": "Identify the current context with a distinct ID.", + "details": "", + "category": "Identification", + "params": [ + { + "name": "distinct_id", + "description": "The distinct ID to associate with the current context and its children", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import identify_context\nidentify_context(\"user_123\")" + } + ] + }, + { + "id": "join", + "title": "join", + "description": "Attempt to process queued events and stop the client's background workers. Use `shutdown()` directly in most cases. Failed or undrainable events may be dropped and reported through logging or ``on_error``; returning does not guarantee server receipt. Lifecycle cleanup is attempted once, and cleanup failures are logged without retry.", + "details": "", + "category": "Client management", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import join\njoin()" + } + ] + }, + { + "id": "load_feature_flags", + "title": "load_feature_flags", + "description": "Load feature flag definitions from PostHog.", + "details": "", + "category": "Feature flags", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import load_feature_flags\nload_feature_flags()" + } + ] + }, + { + "id": "new_context", + "title": "new_context", + "description": "Create a new context scope that will be active for the duration of the with block.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "fresh", + "description": "Whether to start with a fresh context (default: False)", + "isOptional": false, + "type": "bool" + }, + { + "name": "capture_exceptions", + "description": "Whether to capture exceptions raised within the context. If omitted, defaults to the relevant client's exception autocapture setting.", + "isOptional": true, + "type": "bool" + }, + { + "name": "client", + "description": "Optional Posthog client instance to use for this context (default: None)", + "isOptional": true, + "type": "Client" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import new_context, tag, capture\nwith new_context():\n tag(\"request_id\", \"123\")\n capture(\"event_name\", properties={\"property\": \"value\"})" + } + ] + }, + { + "id": "scoped", + "title": "scoped", + "description": "Decorator that creates a new context for the function.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "fresh", + "description": "Whether to start with a fresh context (default: False)", + "isOptional": false, + "type": "bool" + }, + { + "name": "capture_exceptions", + "description": "Whether to capture and track exceptions with posthog error tracking. If omitted, defaults to the global exception autocapture setting.", + "isOptional": true, + "type": "bool" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import scoped, tag, capture\n@scoped()\ndef process_payment(payment_id):\n tag(\"payment_id\", payment_id)\n capture(\"payment_started\")" + } + ] + }, + { + "id": "set", + "title": "set", + "description": "Set properties on a user record.", + "details": "This will overwrite previous people property values. Generally operates similar to `capture`, with distinct_id being an optional argument, defaulting to the current context's distinct ID. If there is no context-level distinct ID, and no override distinct_id is passed, this function will do nothing. Context tags are folded into $set properties, so tagging the current context and then calling `set` will cause those tags to be set on the user (unlike capture, which causes them to just be set on the event).", + "category": "Identification", + "params": [ + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalSetArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Set person properties", + "code": "# Set person properties\nfrom posthog import set\nset(distinct_id='distinct_id', properties={'name': 'Max Hedgehog'})" + } + ] + }, + { + "id": "set_capture_exception_code_variables_context", + "title": "set_capture_exception_code_variables_context", + "description": "Override code-variable capture for exceptions in the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "enabled", + "description": "Whether exceptions captured in this context should include local variable values from stack frames.", + "isOptional": true, + "type": "bool" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "set_code_variables_detect_secrets_context", + "title": "set_code_variables_detect_secrets_context", + "description": "Whether to apply entropy-based secret detection as a last-resort redaction of high-entropy values (API keys, tokens, strong passwords) in captured code variables for the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "enabled", + "description": "", + "isOptional": true, + "type": "bool" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "set_code_variables_ignore_patterns_context", + "title": "set_code_variables_ignore_patterns_context", + "description": "Override code-variable ignore patterns for exceptions in the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "ignore_patterns", + "description": "Variable-name patterns that should be omitted entirely when code variables are captured.", + "isOptional": true, + "type": "list[str]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "set_code_variables_mask_patterns_context", + "title": "set_code_variables_mask_patterns_context", + "description": "Override code-variable mask patterns for exceptions in the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "mask_patterns", + "description": "Variable-name patterns whose values should be replaced with ``***`` when code variables are captured.", + "isOptional": true, + "type": "list[str]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "set_code_variables_mask_url_credentials_context", + "title": "set_code_variables_mask_url_credentials_context", + "description": "Whether to scrub credentials embedded in URLs/DSNs (e.g. user:pass@host) from captured code variables for the current context.", + "details": "", + "category": null, + "params": [ + { + "name": "enabled", + "description": "", + "isOptional": true, + "type": "bool" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "set_context_device_id", + "title": "set_context_device_id", + "description": "Set the device ID for the current context, associating all feature flag requests in this or child contexts with the given device ID.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "device_id", + "description": "The device ID to associate with the current context and its children", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import set_context_device_id\nset_context_device_id(\"device_123\")" + } + ] + }, + { + "id": "set_context_session", + "title": "set_context_session", + "description": "Set the session ID for the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "session_id", + "description": "The session ID to associate with the current context and its children", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import set_context_session\nset_context_session(\"session_123\")" + } + ] + }, + { + "id": "set_once", + "title": "set_once", + "description": "Set properties on a user record, only if they do not yet exist.", + "details": "This will not overwrite previous people property values, unlike `set`. Otherwise, operates in an identical manner to `set`.", + "category": "Identification", + "params": [ + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalSetArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Set property once", + "code": "# Set property once\nfrom posthog import set_once\nset_once(distinct_id='distinct_id', properties={'initial_url': '/blog'})" + } + ] + }, + { + "id": "setup", + "title": "setup", + "description": "Create or return the global PostHog client configured by module settings. Most applications should either instantiate ``Posthog`` directly or set ``posthog.api_key``/other module settings before calling top-level helpers. ``setup()`` is called automatically by global APIs such as ``capture()``. Returns: The global ``Client`` instance. If both ``api_key`` and ``project_api_key`` are missing or blank, the client is disabled and module-level calls become no-ops.", + "details": "", + "category": "Initialization", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Client" + } + }, + { + "id": "shutdown", + "title": "shutdown", + "description": "Attempt final delivery and permanently shut down the global client. Call this for final process cleanup, not at the end of each request or serverless invocation that shares the module-level client. Use ``flush()`` before returning from those invocations instead. Shutdown closes capture lanes, stops background workers and the feature flag poller, and tears down integrations. The global client is not recreated automatically after shutdown. This normally blocks until queued events have been attempted and cleanup finishes. Failed or undrainable events may be dropped and reported through logging or ``on_error``; returning does not guarantee server receipt. Event draining has no timeout. Queued spans get a final flush with up to 30 seconds waiting for an in-flight flush, then a fresh 30-second drain budget; the last HTTP request is not cut short. Unsent queued spans and still-open spans are discarded with a warning. Lifecycle cleanup is attempted once, and cleanup failures are logged without retry. Calls made directly from SDK callbacks such as ``on_error`` are deferred to avoid deadlocking the worker. If blocking completion is required, signal an application-owned thread, return from the callback, and call ``shutdown()`` from that thread. Do not wait inside a callback for another thread or task calling a lifecycle method.", + "details": "", + "category": "Client management", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import shutdown\nshutdown()" + } + ] + }, + { + "id": "start_span", + "title": "start_span", + "description": "Start a span for distributed tracing. Alpha. Returns a span handle. Use it as a context manager to make it the active span for the block and end it on exit (recording a raised exception on the way out); or call ``end()`` yourself for a span that cannot wrap a block. Spans started inside the block nest under it automatically. Always returns a usable handle, even when tracing is off, so calling code never branches.", + "details": "", + "category": "Tracing", + "params": [ + { + "name": "name", + "description": "A low-cardinality operation name, e.g. ``GET /users/:id``. Variable values belong in attributes, not the name.", + "isOptional": true, + "type": "str" + }, + { + "name": "kind", + "description": "``internal`` (default), ``server``, ``client``, ``producer`` or ``consumer``.", + "isOptional": true, + "type": "str" + }, + { + "name": "attributes", + "description": "Initial attributes.", + "isOptional": true, + "type": "Mapping[str, Any]" + }, + { + "name": "parent", + "description": "A span handle, or an inbound W3C ``traceparent`` header value to continue a remote trace. Defaults to the active span.", + "isOptional": false, + "type": "Span" + }, + { + "name": "tracestate", + "description": "The inbound ``tracestate`` header accompanying a ``traceparent`` string ``parent``; preserved and propagated.", + "isOptional": true, + "type": "str" + }, + { + "name": "start_time", + "description": "A ``datetime`` or epoch seconds, to backdate the span.", + "isOptional": false, + "type": "datetime" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Span" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "import posthog\nposthog.traces = {\"service_name\": \"checkout-api\"}\n\nwith posthog.start_span(\"POST /checkout\", parent=request.headers.get(\"traceparent\")) as span:\n span.set_attribute(\"plan\", user.plan)\n with posthog.start_span(\"db.query\", kind=\"client\"):\n ...\n outgoing_headers = {\"traceparent\": span.traceparent()}" + } + ] + }, + { + "id": "tag", + "title": "tag", + "description": "Add a tag to the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "name", + "description": "The tag key", + "isOptional": true, + "type": "str" + }, + { + "name": "value", + "description": "The tag value", + "isOptional": true, + "type": "Any" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import tag\ntag(\"user_id\", \"123\")" + } + ] + } + ] + } + ], + "categories": [ + "Initialization", + "Identification", + "Capture", + "Error Tracking", + "Feature flags", + "Tracing", + "Contexts", + "Events", + "Client management" + ] +} \ No newline at end of file diff --git a/references/posthog-python-references-latest.json b/references/posthog-python-references-latest.json index e6b6c343..bacd9eaf 100644 --- a/references/posthog-python-references-latest.json +++ b/references/posthog-python-references-latest.json @@ -2,7 +2,7 @@ "id": "posthog-python", "hogRef": "0.3", "info": { - "version": "7.75.7", + "version": "7.75.8", "id": "posthog-python", "title": "PostHog Python SDK", "description": "Integrate PostHog into any python application.", From 95139315c93f7e99309f0457e775aa00419c5c31 Mon Sep 17 00:00:00 2001 From: alexcastgonz <82875882+pcbeingused333@users.noreply.github.com> Date: Fri, 23 Oct 2026 06:47:19 -0400 Subject: [PATCH 08/60] fix(examples): use gpt-image-2.5-flare in the OpenAI image generation example (#1109) OpenAI shuts down gpt-image-1 on 2026-10-23 and lists gpt-image-2.5-sunburst or gpt-image-2.5-flare as the replacement (https://developers.openai.com/api/docs/deprecations). Co-authored-by: Claude Sonnet 5.5 (cherry picked from commit efc866ec4b18115518597bfa9f6b82a6f17cc270) --- examples/example-ai-openai/image_generation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/example-ai-openai/image_generation.py b/examples/example-ai-openai/image_generation.py index 8948bb42..9e82ea3b 100644 --- a/examples/example-ai-openai/image_generation.py +++ b/examples/example-ai-openai/image_generation.py @@ -31,7 +31,7 @@ client = openai.OpenAI(api_key=os.environ["OPENAI_API_KEY"]) response = client.images.generate( - model="gpt-image-1", + model="gpt-image-2.5-flare", prompt="A hedgehog wearing a PostHog t-shirt, pixel art style", size="1024x1024", ) From d38d5d21cd6a61f012aa1091e55ca6b84f363aff Mon Sep 17 00:00:00 2001 From: Georgis Andonis <6430745+gesh@users.noreply.github.com> Date: Fri, 23 Oct 2026 15:10:22 +0300 Subject: [PATCH 09/60] fix(mcp): send one $mcp_initialize per initialize request (#1103) * fix(mcp): send one $mcp_initialize per initialize request The Python MCP SDK answers initialize itself, so posthog.mcp sent the event on the first request of each session, once per process. A stateless fleet sent one event per pod. A client that sent only initialize sent no event. Now MCP SDK 2.x reports the event from Server.middleware, on every transport. MCP SDK 1.x over HTTP reports it from PostHogMcpStatelessSessionMiddleware, which takes a new optional server= argument. MCP SDK 1.x over stdio keeps the first-request fallback. Co-Authored-By: Claude Opus 5.5 * refactor(mcp): read only the initialize response head on mcp 1.x The mcp 1.x hook now keeps only the status and the Mcp-Session-Id header of the initialize response. It no longer parses the JSON or SSE body. The event uses the protocol version that the client asked for. record_initialize now reads the client name, version, and protocol from the initialize params and reply, so both hooks pass them through. The new tests share their request helpers and drop the SSE body case. Co-Authored-By: Claude Opus 5.5 * fix(mcp): address review on the initialize hooks - SSE transport: the initialize event now lands in the session that the SSE calls use. On SDK 1.x the middleware leaves SSE requests to the first-request fallback. On SDK 2.x the hook uses the process session. - The bound middleware marks each request that it passes on. The fallback now stops only for marked requests, not for every HTTP request. - The hooks compute the handshake session without a write to the shared session that McpAnalytics.capture() reads. - A server= value that cannot be bound no longer breaks requests. - Remove the unused _mint_token_if_initialize. Co-Authored-By: Claude Opus 5.5 * fix(mcp): wait for the initialize capture inside its handshake task Both handshake hooks run record_initialize in a tracked background task. It started the capture as a second task, after flush() took its snapshot of pending tasks. With a slow before_send, flush() returned early and shutdown dropped $mcp_initialize. The handshake task now awaits the capture itself. Co-Authored-By: Claude Opus 5.5 * fix(mcp): resolve the v2 handshake session from an incoming header On MCP SDK 2.x, a stateless client can send initialize with an existing Mcp-Session-Id and keep sending it. Its later calls resolve their session from that header, but the handshake hook read only the connection id and the minted token. The event landed in a generated session. The hook now falls back to the incoming header. Co-Authored-By: Claude Opus 5.5 * fix(mcp): capture a v1 initialize that carries a session header On MCP SDK 1.x, the middleware read the body only of a POST with no Mcp-Session-Id, because only those need a token. A client that sent initialize with an existing session id got no event: the middleware marked the request, so the first-request fallback did not run either. A middleware bound to a server now reads those bodies too, behind a cheap check for "initialize". It still mints only without a header. The event uses the header that the client keeps sending. Co-Authored-By: Claude Opus 5.5 * fix(mcp): pass a disconnect mid-body through the session middleware When the client disconnected while it sent the body, the middleware stopped its read and replayed the partial body as a complete request. The app parsed half a request and never saw the disconnect. The middleware now replays the partial body with more_body set, then hands the app the disconnect. Co-Authored-By: Claude Opus 5.5 * fix(mcp): stop buffering tool calls once they are not an initialize A bound middleware reads the body of a POST that carries a session id, to catch an initialize that reuses one. That held every tool call in memory, up to 256 KB, before the app saw it. The middleware now stops after 4 KB when "initialize" is not in it, and streams the rest. JSON-RPC clients put "method" before "params", so a handshake shows up in that window. Co-Authored-By: Claude Opus 5.5 * refactor(mcp): read the v2 handshake header through _resolve_ctx The v2 handshake hook read the Mcp-Session-Id header and decoded the token itself. Every later request gets the same pair from _resolve_ctx. The hook now calls it, so the handshake and later requests follow one rule and cannot drift apart into two sessions. Co-Authored-By: Claude Opus 5.5 * fix(mcp): run the initialize callbacks inline in the handshake request Both handshake hooks ran identify and event_properties in a detached background task. If a callback never returned, each handshake left one more task pending, with no bound, and flush() waited for all of them. The spec requires pending state to be bounded by count. The hooks now run the callbacks inline, the same as a tool call does. A hung callback holds that request, where the host sees it, and leaves no task behind. The capture still runs in the background after the callbacks, as for every other event. Co-Authored-By: Claude Opus 5.5 * fix(mcp): record the v1 handshake inside the request's auth context The session middleware sits outside FastMCP's AuthContextMiddleware. It ran identify after the app returned, when that middleware had already reset the authenticated user. An identify that calls get_access_token() got None, so $mcp_initialize stayed anonymous under OAuth. The middleware now records the handshake when the response starts, while the app still handles the request. Co-Authored-By: Claude Opus 5.5 * fix(mcp): stop the body peek only on a method that is not initialize The 4 KB peek stopped when "initialize" was not in the first 4 KB. JSON key order is arbitrary, so an initialize with large params before its method got no event. The peek now stops only when the prefix shows a "method" and no "initialize". Without a method yet, it reads up to the full cap. Co-Authored-By: Claude Opus 5.5 * fix(mcp): drop the body byte checks from the session middleware The middleware guessed from raw bytes whether a request was initialize. A nested "method" key in params stopped the 4 KB peek too early, and a JSON-escaped "initialize" failed the quick byte check. That second case also stopped the token mint, which worked before this PR. The middleware now parses the body as JSON, as main does, and reads only requests with no Mcp-Session-Id. On MCP SDK 1.x, an initialize that reuses an existing session id is not captured. The README lists this limit. MCP SDK 2.x still captures it. Co-Authored-By: Claude Opus 5.5 --------- Co-authored-by: Claude Opus 5.5 (cherry picked from commit 8cd8eb5b6fdcdb19db4c4d07fe911c6eb3976d5a) --- .../mcp-initialize-per-handshake.md | 11 + posthog/mcp/README.md | 32 +- posthog/mcp/__init__.py | 15 +- posthog/mcp/_instrument_v2.py | 90 +- posthog/mcp/_instrumentation.py | 95 +- posthog/mcp/_internal.py | 17 + posthog/mcp/asgi.py | 203 +++- posthog/mcp/session.py | 19 + posthog/test/mcp/test_initialize_handshake.py | 990 ++++++++++++++++++ posthog/test/mcp/test_session_token.py | 30 + posthog/test/mcp/test_v2_lowlevel.py | 6 +- posthog/test/mcp/test_v2_mcpserver.py | 6 +- references/public_api_snapshot.txt | 3 +- 13 files changed, 1464 insertions(+), 53 deletions(-) create mode 100644 .sampo/changesets/mcp-initialize-per-handshake.md create mode 100644 posthog/test/mcp/test_initialize_handshake.py diff --git a/.sampo/changesets/mcp-initialize-per-handshake.md b/.sampo/changesets/mcp-initialize-per-handshake.md new file mode 100644 index 00000000..33d926d0 --- /dev/null +++ b/.sampo/changesets/mcp-initialize-per-handshake.md @@ -0,0 +1,11 @@ +--- +pypi/posthog: patch +--- + +MCP analytics now sends one `$mcp_initialize` for each `initialize` request that a client sends. + +Before, the SDK sent the event on the first request of each session, once per process. A stateless fleet sent one event for each pod that a session reached. A conversation-anchored session sent another event. A client that connected and sent no other request sent no event. + +On MCP SDK 2.x, the event comes from `Server.middleware` on every transport. Requests on the 2026-07-28 spec, which has no `initialize`, no longer send one. On MCP SDK 1.x over HTTP, the event comes from `PostHogMcpStatelessSessionMiddleware`. The apps that `instrument()` autowires get this with no change. If you mount the middleware by hand, pass the server as `server=`. MCP SDK 1.x over stdio keeps the first request as the trigger. + +The event now also has the handshake request as `$mcp_parameters`. On MCP SDK 2.x, it also has the server reply as `$mcp_response`. diff --git a/posthog/mcp/README.md b/posthog/mcp/README.md index 0e8cfe1a..48b1f281 100644 --- a/posthog/mcp/README.md +++ b/posthog/mcp/README.md @@ -66,6 +66,31 @@ The value must contain 1 to 256 characters. The SDK records it as `$mcp_server_build` on all MCP events. `PostHogMCP.capture()` also adds it to custom events unless the event provides its own value. +## When the SDK sends `$mcp_initialize` + +One `$mcp_initialize` per `initialize` request the client sends, in the session +its later requests belong to. A client that reconnects five times reports five, +however many pods its requests land on. A client that connects and leaves still +reports one. Requests on the 2026-07-28 revision, which has no `initialize`, +report none. + +The MCP SDK answers `initialize` itself, so the SDK observes it from outside: + +- **MCP SDK 2.x**: through `Server.middleware`, on every transport. +- **MCP SDK 1.x over HTTP**: through `PostHogMcpStatelessSessionMiddleware`, which + `instrument()` adds to the apps it autowires. If you mount the middleware + yourself, pass the server: `app.add_middleware(PostHogMcpStatelessSessionMiddleware, server=mcp)`. + Without it, each pod reports the session's first request as its initialize. + The deprecated SSE transport also uses the session's first request. + The middleware reads only requests that carry no `Mcp-Session-Id`, so an + `initialize` that reuses an existing session id reports none. +- **MCP SDK 1.x over stdio**: from the session's first request. A stdio server + serves one client, so this misses only a client that never sends a request. + +`identify` and `event_properties` run for the handshake too. On MCP SDK 1.x the +SDK has no request context for it, so `extra["ctx"]` carries only `.request`. +`get_request_headers(extra)` works the same as for any other request. + ## Capture safe input field names Tool-call events include `$mcp_input_keys`. The SDK records names from the @@ -384,11 +409,14 @@ a fragmented per-process id. Add the middleware to your app explicitly: from posthog.mcp import PostHogMcpStatelessSessionMiddleware, get_mcp_session app = mcp.streamable_http_app() -app.add_middleware(PostHogMcpStatelessSessionMiddleware) +app.add_middleware(PostHogMcpStatelessSessionMiddleware, server=mcp) ``` +`server=` lets the middleware capture `$mcp_initialize` from the handshake (see +[When the SDK sends `$mcp_initialize`](#when-the-sdk-sends-mcp_initialize)). + This is also the path for a custom `PostHogMCP` dispatcher (you own the ASGI app), -where you then read the recovered session per request: +where you leave `server=` out and read the recovered session per request: ```python sess = get_mcp_session(request) # sess.session_id, sess.client_name, ... diff --git a/posthog/mcp/__init__.py b/posthog/mcp/__init__.py index 015c9ee5..9c2ca315 100644 --- a/posthog/mcp/__init__.py +++ b/posthog/mcp/__init__.py @@ -51,6 +51,7 @@ MCPAnalyticsData, get_server_tracking_data, set_server_tracking_data, + tracking_key, ) from .logger import log, set_logger from .posthog_mcp import PostHogMCP @@ -226,18 +227,6 @@ def _warn_if_unsupported_mcp_version() -> None: ) -def _canonical_server(server: Any) -> Any: - """The underlying low-level server for high-level wrappers (SDK 1.x FastMCP and - jlowin's fastmcp expose ``_mcp_server``; SDK 2.x MCPServer renamed it - ``_lowlevel_server``), else the server itself. Used as the tracking key so - instrumenting a wrapper and its underlying server resolve to one state instead - of two divergent ones (matching the TS SDK).""" - low_level = getattr(server, "_mcp_server", None) or getattr( - server, "_lowlevel_server", None - ) - return low_level if low_level is not None else server - - def instrument( server: Any, posthog_client: Optional[Client] = None, @@ -286,7 +275,7 @@ def instrument( if feedback_options is not None: get_feedback_tool_descriptor(feedback_options) - key = _canonical_server(server) + key = tracking_key(server) try: # MCP is an optional peer: load adapters only when instrumentation is diff --git a/posthog/mcp/_instrument_v2.py b/posthog/mcp/_instrument_v2.py index 843f2d03..58d45f77 100644 --- a/posthog/mcp/_instrument_v2.py +++ b/posthog/mcp/_instrument_v2.py @@ -33,6 +33,7 @@ import time from collections.abc import Mapping from dataclasses import replace +from datetime import datetime, timezone from typing import Any, Dict, FrozenSet, Optional, Set, Tuple import mcp.types as mcp_types @@ -48,12 +49,15 @@ advertised_tool_names, apply_virtual_tool_injection, collect_listed_tools, + _is_sse_request, + _to_jsonable, copy_tools_list_result, is_first_listing_page, mutate_tool_schema, params_to_request_dict, prepare_request, raw_listing_owns_tool_name, + record_initialize, record_resource_request, resource_listing_response, resolve_session_and_client, @@ -70,9 +74,11 @@ ) from ._output_instructions import mirror_instructions_into_structured_content from ._tool_input import get_registered_tool_input_schema +from .asgi import get_mcp_session from .logger import log from .request_headers import get_request_headers -from .session_token import read_mcp_session_header +from .session import handshake_session_id +from .session_token import SessionTokenPayload, read_mcp_session_header from .tools import ( get_more_tools_result_text, ) @@ -111,6 +117,7 @@ def instrument_mcpserver_v2(server: Any, data: MCPAnalyticsData) -> None: for method, event_type in _RESOURCE_METHODS.items(): _wrap_v2_resource_request(low_level, data, method, event_type) _patch_add_request_handler(low_level, data, wrap_call=False, high_level=server) + _observe_initialize(low_level, data) def instrument_lowlevel_v2(server: Any, data: MCPAnalyticsData) -> None: @@ -126,6 +133,87 @@ def instrument_lowlevel_v2(server: Any, data: MCPAnalyticsData) -> None: for method, event_type in _RESOURCE_METHODS.items(): _wrap_v2_resource_request(server, data, method, event_type) _patch_add_request_handler(server, data, wrap_call=True) + _observe_initialize(server, data) + + +# --- initialize ------------------------------------------------------------------ + + +def _observe_initialize(server: Any, data: MCPAnalyticsData) -> None: + """Emit ``$mcp_initialize`` from the real handshake via ``Server.middleware``. + + mcp 2.x refuses a request handler for ``initialize`` and points at + ``Server.middleware`` instead, which wraps every inbound request on every + transport, ``initialize`` included. The host callbacks run inline, as they do + for a tool call: a hung ``identify`` hangs this handshake, where the client + sees it, and leaves no task behind. The runner parks the connection's read + loop until the chain returns, but a client sends nothing else before the + ``initialize`` reply.""" + middleware = getattr(server, "middleware", None) + if not isinstance(middleware, list): + log( + "Warning: this MCP server has no Server.middleware list; " + "$mcp_initialize falls back to the first request of each session." + ) + return + if not any(getattr(entry, _WRAPPED_FLAG, False) for entry in middleware): + + async def observe_initialize(ctx: Any, call_next: Any) -> Any: + if getattr(ctx, "method", None) != "initialize": + return await call_next(ctx) + started = datetime.now(timezone.utc) + result = await call_next(ctx) + await _record_v2_initialize(data, ctx, result, started) + return result + + setattr(observe_initialize, _WRAPPED_FLAG, True) + middleware.append(observe_initialize) + data.handshake_observed_in_process = True + + +def _initialize_session( + ctx: Any, +) -> Tuple[Optional[str], Optional[SessionTokenPayload]]: + """The ``(mcp_session_id, token)`` every later request of this connection will + resolve its session from. + + A stateful transport has already assigned the id it returns in the + ``Mcp-Session-Id`` header; ``ServerSession`` keeps it on its connection and + exposes no public accessor. A stateless one has none: the session comes from + the ``Mcp-Session-Id`` the client sent, which it keeps sending, read the way + :func:`_resolve_ctx` reads it for every later request, or else from the token + our ASGI middleware minted for this response. stdio has neither and resolves + to this process's own session, as its later requests do.""" + if _is_sse_request({"ctx": ctx}): + # The SSE transport keys its session on a query parameter that later + # requests do not resolve from: they land in this process's own session. + return None, None + connection = getattr(getattr(ctx, "session", None), "_connection", None) + transport_session_id = getattr(connection, "session_id", None) + if isinstance(transport_session_id, str) and transport_session_id: + return transport_session_id, None + token, _, _, _, incoming = _resolve_ctx(ctx) + if incoming: + return incoming, token + request = getattr(ctx, "request", None) + return None, get_mcp_session(request) if request is not None else None + + +async def _record_v2_initialize( + data: MCPAnalyticsData, ctx: Any, result: Any, started: datetime +) -> None: + try: + mcp_session_id, token = _initialize_session(ctx) + await record_initialize( + data, + session_id=await handshake_session_id(data, mcp_session_id, token), + params=dict(ctx.params) if isinstance(ctx.params, Mapping) else {}, + result=_to_jsonable(result), + extra={"session_id": mcp_session_id, "ctx": ctx}, + timestamp=started, + ) + except Exception as error: # noqa: BLE001 - analytics must never break the handshake + log(f"Error: failed to capture $mcp_initialize - {error}") # --- registry plumbing --------------------------------------------------------- diff --git a/posthog/mcp/_instrumentation.py b/posthog/mcp/_instrumentation.py index 03b66392..4e72e1de 100644 --- a/posthog/mcp/_instrumentation.py +++ b/posthog/mcp/_instrumentation.py @@ -240,17 +240,89 @@ def _wrap_response(result: Any) -> Any: return serialized +async def record_initialize( + data: MCPAnalyticsData, + *, + session_id: str, + params: Dict[str, Any], + result: Any, + extra: Optional[Dict[str, Any]], + timestamp: datetime, +) -> None: + """Emit ``$mcp_initialize`` for an ``initialize`` the client sent. + + The Python MCP SDK answers ``initialize`` itself (mcp 1.x inside + ``ServerSession``; mcp 2.x refuses a handler for it), so the seams that observe + it — ``Server.middleware`` on 2.x, the ASGI middleware on 1.x over HTTP — call + this inline in the handshake request once it has succeeded. ``result`` is the + server's reply where the seam can read it, else ``None``. Identify runs first + so the event is attributed on the pod that served the handshake, which on a + stateless fleet may never see this client again. The capture itself runs after, + in the background, as for every other event.""" + + def text(value: Any) -> Optional[str]: + return value if isinstance(value, str) and value else None + + client_info = params.get("clientInfo") + client_info = client_info if isinstance(client_info, dict) else {} + agreed = result.get("protocolVersion") if isinstance(result, dict) else None + request = {"method": "initialize", "params": params} + await handle_identify(data, session_id, request, extra) + data.mark_session_initialized(session_id) + event: Dict[str, Any] = { + "event_type": MCPAnalyticsEventType.MCP_INITIALIZE, + "session_id": session_id, + "client_name": text(client_info.get("name")), + "client_version": text(client_info.get("version")), + "protocol_version": text(agreed) or text(params.get("protocolVersion")), + "parameters": request, + "response": result, + "timestamp": timestamp, + } + await _apply_event_properties(data, event, request, extra) + stamp_transport_identity(event, extra) + fire_and_forget(capture_event(data, event), data) + + +# ASGI scope key a bound ``PostHogMcpStatelessSessionMiddleware`` sets to the +# tracking state whose handshakes it captures, on each request it passes on. +HANDSHAKE_OBSERVER_KEY = "posthog_mcp_handshake_observer" + + +def _first_request_stands_in_for_initialize( + data: MCPAnalyticsData, session_source: str, extra: Optional[Dict[str, Any]] +) -> bool: + """Whether this request has to stand in for an ``initialize`` nothing observed. + + Only where no seam sees the real handshake: mcp 1.x over stdio (one client per + process, so its first request is as good as the handshake), and an mcp 1.x HTTP + request that did not pass a middleware bound to this server. Never for a + conversation-anchored session: it belongs to a chat, not to a handshake, and + synthesizing one there is how a single connection used to report two. So a + client whose first request already echoes a conversation reports no initialize + here.""" + if data.handshake_observed_in_process: + return False + if session_source == "conversation": + return False + scope = getattr(get_request(extra), "scope", None) + return not (isinstance(scope, dict) and scope.get(HANDSHAKE_OBSERVER_KEY) is data) + + async def _maybe_emit_initialize( data: MCPAnalyticsData, session_id: str, + session_source: str, client_name: Optional[str], client_version: Optional[str], extra: Optional[Dict[str, Any]], protocol_version: Optional[str] = None, ) -> None: - """Lazily emit ``$mcp_initialize`` once per session. The Python MCP SDK handles - ``InitializeRequest`` inside the session layer (not ``request_handlers``), so we - synthesize the event from the first instrumented request that carries client info.""" + """Emit ``$mcp_initialize`` once per session from its first request, where no + seam observes the real ``initialize`` (see + :func:`_first_request_stands_in_for_initialize`).""" + if not _first_request_stands_in_for_initialize(data, session_source, extra): + return if session_id in data.initialized_sessions: return data.mark_session_initialized(session_id) @@ -373,8 +445,9 @@ async def prepare_request( protocol_version: Optional[str] = None, conversation_id: Optional[str] = None, ) -> str: - """Resolve the session id, run identify, then lazily emit initialize. Returns - the session id to stamp on the event for this request. + """Resolve the session id, run identify, then emit initialize if this request + has to stand in for an unobserved handshake. Returns the session id to stamp on + the event for this request. ``conversation_id`` is the agent's handle for this request, and when present it anchors the session (ADR-0004) so every event of the request — initialize @@ -397,8 +470,8 @@ async def prepare_request( Identify runs *before* initialize so the resolved identity is already in the cache when ``capture_event`` builds the initialize event — otherwise the first ``$mcp_initialize`` is anonymous even when identify resolves on the same request. - (Still not byte-parity with the TS SDK, which wraps the real initialize handler; - the Python SDK handles initialize in the session layer, not ``request_handlers``.) + Where a seam observes the real handshake (:func:`record_initialize`), nothing + is synthesized here. A request that reached us over HTTP yet still resolved to this process's memory has nothing correlating it across pods, which on a stateless server means the @@ -414,7 +487,13 @@ async def prepare_request( _warn_stateless_session_not_wired(data) await handle_identify(data, session_id, request, extra) await _maybe_emit_initialize( - data, session_id, client_name, client_version, extra, protocol_version + data, + session_id, + session_source, + client_name, + client_version, + extra, + protocol_version, ) return session_id diff --git a/posthog/mcp/_internal.py b/posthog/mcp/_internal.py index 8da3cd8d..44a1eeb4 100644 --- a/posthog/mcp/_internal.py +++ b/posthog/mcp/_internal.py @@ -107,6 +107,11 @@ class MCPAnalyticsData: # Bounded FIFO of sessions we've emitted $mcp_initialize for, so a long-lived # server can't accumulate one entry per session forever. initialized_sessions: "OrderedDict[str, None]" = field(default_factory=OrderedDict) + # True once mcp 2.x `Server.middleware` observes this server's real + # `initialize`, on every transport. On mcp 1.x an ASGI middleware bound to + # this server observes it over HTTP and marks each request it passes on; + # whatever neither covers falls back to the first request of the session. + handshake_observed_in_process: bool = False server_name: Optional[str] = None # Validated once during setup. Keep it separate from the caller-owned # options object so later option changes cannot alter event attribution. @@ -130,6 +135,18 @@ def mark_session_initialized(self, session_id: str) -> None: ) +def tracking_key(server: Any) -> Any: + """The underlying low-level server for high-level wrappers (SDK 1.x FastMCP and + jlowin's fastmcp expose ``_mcp_server``; SDK 2.x MCPServer renamed it + ``_lowlevel_server``), else the server itself. Used as the tracking key so + instrumenting a wrapper and its underlying server resolve to one state instead + of two divergent ones (matching the TS SDK).""" + low_level = getattr(server, "_mcp_server", None) or getattr( + server, "_lowlevel_server", None + ) + return low_level if low_level is not None else server + + def get_server_tracking_data(server: Any) -> Optional[MCPAnalyticsData]: return _server_tracking.get(server) diff --git a/posthog/mcp/asgi.py b/posthog/mcp/asgi.py index 4e742a14..a66b4111 100644 --- a/posthog/mcp/asgi.py +++ b/posthog/mcp/asgi.py @@ -12,6 +12,11 @@ layer instead, with a pure-ASGI middleware that works for both a mounted FastMCP app and a custom ``PostHogMCP`` dispatcher, across every SDK routing path. +The same blind spot hides the handshake from analytics on MCP SDK 1.x, so a +middleware bound to an instrumented server also captures ``$mcp_initialize`` here, +once per ``initialize``, in the session the response hands the client. (MCP SDK +2.x exposes ``Server.middleware``, which ``instrument()`` uses instead.) + On the ``instrument()`` path this is **wired up automatically**: ``instrument(server, ...)`` wraps the FastMCP server's app factories (``streamable_http_app()`` / ``sse_app()``, which ``mcp.run()`` also calls), so the @@ -38,10 +43,15 @@ import functools import json -from typing import Any, Optional +import weakref +from datetime import datetime, timezone +from typing import Any, Dict, Optional +from urllib.parse import parse_qs +from ._instrumentation import HANDSHAKE_OBSERVER_KEY, record_initialize +from ._internal import MCPAnalyticsData, get_server_tracking_data, tracking_key from .logger import log, warn -from .session import new_session_id +from .session import handshake_session_id, new_session_id from .session_token import ( MCP_SESSION_HEADER, SessionTokenPayload, @@ -73,16 +83,48 @@ class PostHogMcpStatelessSessionMiddleware: response header at ``initialize`` (when the client sent none) and decodes the replayed token on every request, exposing it via :func:`get_mcp_session`. + Pass the instrumented ``server`` to also capture ``$mcp_initialize`` from the + handshake itself on MCP SDK 1.x, which answers ``initialize`` outside anything + ``instrument()`` can wrap. ``instrument()`` does this for the apps it autowires; + add it yourself when you mount the middleware by hand on an instrumented server. + Leave it out for a custom ``PostHogMCP`` dispatcher, which captures its own + events. + Fail-safe: any error while sniffing/minting is logged and the request passes through untouched -- analytics must never break the host.""" - def __init__(self, app: Any) -> None: + def __init__(self, app: Any, server: Any = None) -> None: self.app = app + self._server: Optional[weakref.ReferenceType[Any]] = None + if server is not None: + try: + self._server = weakref.ref(tracking_key(server)) + except TypeError as error: + log(f"PostHog MCP session middleware: cannot bind server= - {error}") + + def _analytics(self) -> Optional[MCPAnalyticsData]: + """The bound server's tracking state, when this middleware is the one that + captures its handshakes: on mcp 2.x ``Server.middleware`` already does.""" + try: + server = self._server() if self._server is not None else None + data = get_server_tracking_data(server) if server is not None else None + except TypeError: # an unhashable server= can never be instrumented + return None + return None if data is None or data.handshake_observed_in_process else data async def __call__(self, scope: Any, receive: Any, send: Any) -> None: if scope.get("type") != "http": await self.app(scope, receive, send) return + # Tell the in-process fallback that this request's handshake is captured + # here. Two stacked middlewares see the same request; the outer one owns it. + # The SSE transport stays unmarked: its session is in a query parameter, so + # its first request captures the handshake, in the session its calls use. + observer = None + if HANDSHAKE_OBSERVER_KEY not in scope and not _is_sse(scope): + observer = self._analytics() + if observer is not None: + scope[HANDSHAKE_OBSERVER_KEY] = observer try: incoming_header = read_mcp_session_header(_headers_dict(scope)) @@ -98,6 +140,8 @@ async def __call__(self, scope: Any, receive: Any, send: Any) -> None: # Only POSTs carry JSON-RPC. Mint only when the client replayed no session # id at all -- if one is present (ours or a stateful transport's), leave it. + # Such a request is not read, so an initialize that reuses a session id is + # not captured here. Tool calls, which all carry one, stay unread. if scope.get("method") != "POST" or incoming_header is not None: await self.app(scope, receive, send) return @@ -105,24 +149,51 @@ async def __call__(self, scope: Any, receive: Any, send: Any) -> None: # Guard the whole sniff+mint path: on any failure (a raising receive() or # adversarial body) the request passes through untouched, per the contract. token = None + initialize = None try: body, receive = await _buffer_body(receive) - token = _mint_token_if_initialize(body) + initialize = _parse_initialize(body) + if initialize is not None: + token = _mint_token(initialize) except Exception as error: # noqa: BLE001 log(f"PostHog MCP session middleware: mint failed - {error}") - if token is None: + if initialize is None: await self.app(scope, receive, send) return - scope[_SCOPE_KEY] = decode_session_id(token) - await self.app(scope, receive, _sending_session_header(send, token)) + started = datetime.now(timezone.utc) + if observer is not None: + send = _InitializeResponse( + send, + functools.partial( + _record_initialize, observer, initialize, scope, started + ), + ) + if token is not None: + scope[_SCOPE_KEY] = decode_session_id(token) + send = _sending_session_header(send, token) + + await self.app(scope, receive, send) + + +def _is_sse(scope: Any) -> bool: + """The deprecated SSE transport, which keys its session on ``?session_id=``.""" + try: + query = parse_qs((scope.get("query_string") or b"").decode("latin-1")) + except Exception: # noqa: BLE001 + return False + return "session_id" in query def _headers_dict(scope: Any) -> dict[str, str]: """ASGI raw headers (list of (bytes, bytes)) -> a lowercased str dict.""" + return _decode_headers(scope.get("headers") or []) + + +def _decode_headers(raw: Any) -> dict[str, str]: result: dict[str, str] = {} - for key, value in scope.get("headers") or []: + for key, value in raw: try: result[key.decode("latin-1").lower()] = value.decode("latin-1") except Exception: # noqa: BLE001 @@ -143,10 +214,13 @@ async def _buffer_body(receive: Any) -> tuple[bytes, Any]: total = 0 complete = False overflow = False + # A non-body message (e.g. http.disconnect) ends the read early. It is kept + # and handed to the app after the prefix, so the app sees the disconnect. + held: Optional[dict[str, Any]] = None while True: message = await receive() if message.get("type") != "http.request": - # A non-body message (e.g. http.disconnect); stop with what we have. + held = message break chunks.append(message.get("body", b"") or b"") total += len(chunks[-1]) @@ -162,11 +236,14 @@ async def _buffer_body(receive: Any) -> tuple[bytes, Any]: async def replay() -> dict[str, Any]: # Replay the buffered prefix once; if we stopped early, flag more_body so - # the app keeps pulling the rest straight from the original transport. - nonlocal replayed + # the app keeps reading: the held message, then the original transport. + nonlocal replayed, held if not replayed: replayed = True - return {"type": "http.request", "body": buffered, "more_body": overflow} + return {"type": "http.request", "body": buffered, "more_body": not complete} + if held is not None: + message, held = held, None + return message return await receive() # Only sniff (parse for `initialize`) when we captured the whole small body. @@ -174,10 +251,9 @@ async def replay() -> dict[str, Any]: return sniff, replay -def _mint_token_if_initialize(body: bytes) -> Optional[str]: - """If ``body`` is an ``initialize`` JSON-RPC request, mint and return a token - string carrying a fresh session id + the client's self-reported identity. - Returns ``None`` for anything else (never raises).""" +def _parse_initialize(body: bytes) -> Optional[Dict[str, Any]]: + """The JSON-RPC message in ``body`` if it is an ``initialize`` request, else + ``None`` (never raises).""" if not body: return None try: @@ -188,10 +264,13 @@ def _mint_token_if_initialize(body: bytes) -> Optional[str]: return None if not isinstance(message, dict) or message.get("method") != "initialize": return None - params = message.get("params") - params = params if isinstance(params, dict) else {} - client_info = params.get("clientInfo") - client_info = client_info if isinstance(client_info, dict) else {} + return message + + +def _mint_token(message: Dict[str, Any]) -> Optional[str]: + """Mint a token carrying a fresh session id + the client's self-reported + identity from an ``initialize`` message. Returns ``None`` on failure.""" + params, client_info = _initialize_params(message) try: return encode_session_id( SessionTokenPayload( @@ -206,6 +285,82 @@ def _mint_token_if_initialize(body: bytes) -> Optional[str]: return None +def _initialize_params( + message: Dict[str, Any], +) -> tuple[Dict[str, Any], Dict[str, Any]]: + params = message.get("params") + params = params if isinstance(params, dict) else {} + client_info = params.get("clientInfo") + return params, client_info if isinstance(client_info, dict) else {} + + +class _InitializeResponse: + """A ``send`` that records the handshake when the ``initialize`` response + starts, then passes the response on. The app calls it from inside its own + request context, so an auth middleware's user is still set for ``identify``. + The start carries the status and the ``Mcp-Session-Id`` the client will send + back (whichever of the transport or the mint set it). The body is not read.""" + + def __init__(self, send: Any, record: Any) -> None: + self._send = send + self._record = record + + async def __call__(self, message: Dict[str, Any]) -> None: + if message.get("type") == "http.response.start" and self._record is not None: + record, self._record = self._record, None + headers = _decode_headers(message.get("headers") or []) + await record( + message.get("status"), headers.get(MCP_SESSION_HEADER.lower()) or None + ) + await self._send(message) + + +class _InitializeContext: + """``extra["ctx"]`` for callbacks run on an ``initialize`` observed here. + + MCP SDK 1.x answers ``initialize`` inside its session layer, outside any request + context, so there is no SDK context object to hand over. This carries only the + HTTP request, which is what :func:`~posthog.mcp.get_request_headers` reads.""" + + def __init__(self, request: Any) -> None: + self.request = request + + +async def _record_initialize( + data: MCPAnalyticsData, + message: Dict[str, Any], + scope: Any, + started: datetime, + status: Optional[int], + session_header: Optional[str], +) -> None: + """Emit ``$mcp_initialize`` for a handshake the app answered, in the session the + client's later requests resolve to: the ``Mcp-Session-Id`` they send back (a + stateful transport's id or our token), or this process's own session when the + response carried none. The body is not read, so the + event carries the protocol version the client asked for.""" + try: + if status is None or not 200 <= status < 300: + return + from starlette.requests import Request + + header = session_header + await record_initialize( + data, + session_id=await handshake_session_id( + data, header, decode_session_id(header) + ), + params=_initialize_params(message)[0], + result=None, + extra={"session_id": header, "ctx": _InitializeContext(Request(scope))}, + timestamp=started, + ) + except Exception as error: # noqa: BLE001 - analytics must never break the host + log( + f"PostHog MCP session middleware: failed to capture $mcp_initialize - {error}" + ) + + def _sending_session_header(send: Any, token: str) -> Any: """Wrap ``send`` so the outgoing response start carries ``Mcp-Session-Id: token`` -- but only if the app/transport didn't already set one (never clobber a @@ -251,7 +406,7 @@ def autowire_stateless_mint(server: Any) -> None: if not callable(original) or getattr(original, _AUTOWIRED, False): continue try: - setattr(server, attr, _wrap_app_factory(original)) + setattr(server, attr, _wrap_app_factory(original, server)) except Exception as error: # noqa: BLE001 - never let wiring break instrument() log(f"PostHog MCP: could not auto-wire stateless mint on {attr} - {error}") @@ -312,7 +467,7 @@ def _app_already_wrapped(app: Any) -> bool: return False -def _wrap_app_factory(original: Any) -> Any: +def _wrap_app_factory(original: Any, server: Any = None) -> Any: @functools.wraps(original) def factory(*args: Any, **kwargs: Any) -> Any: app = original(*args, **kwargs) @@ -320,10 +475,10 @@ def factory(*args: Any, **kwargs: Any) -> Any: return app add_middleware = getattr(app, "add_middleware", None) if callable(add_middleware): - add_middleware(PostHogMcpStatelessSessionMiddleware) + add_middleware(PostHogMcpStatelessSessionMiddleware, server=server) return app # Not a Starlette app -- wrap as raw ASGI so minting still happens. - return PostHogMcpStatelessSessionMiddleware(app) + return PostHogMcpStatelessSessionMiddleware(app, server=server) setattr(factory, _AUTOWIRED, True) return factory diff --git a/posthog/mcp/session.py b/posthog/mcp/session.py index dc59f51f..4da6a627 100644 --- a/posthog/mcp/session.py +++ b/posthog/mcp/session.py @@ -60,6 +60,25 @@ async def resolve_session_id( return session_id +async def handshake_session_id( + data: MCPAnalyticsData, + mcp_session_id: Optional[str], + token: Optional[SessionTokenPayload], +) -> str: + """The session an ``initialize`` belongs to: the one the client's later requests + resolve to through :func:`resolve_session_id_with_source`. + + A token or a transport session id gives it directly, so this leaves the server's + shared session alone. One server serves many clients, and a handshake must not + move the session that ``McpAnalytics.capture()`` reads. Without either, the + session is this process's own, which only the resolver can hand out.""" + if token is not None: + return token.session_id + if mcp_session_id: + return derive_session_id_from_mcp_session(mcp_session_id) + return await resolve_session_id(data, None) + + async def resolve_session_id_with_source( data: MCPAnalyticsData, mcp_session_id: Optional[str], diff --git a/posthog/test/mcp/test_initialize_handshake.py b/posthog/test/mcp/test_initialize_handshake.py new file mode 100644 index 00000000..022235fa --- /dev/null +++ b/posthog/test/mcp/test_initialize_handshake.py @@ -0,0 +1,990 @@ +"""``$mcp_initialize`` is one event per ``initialize`` the client actually sent. + +The spec (sdk-specs ``mcp-analytics``) allows ``$mcp_initialize`` only for a legacy +``initialize`` request, and never without one. Python cannot wrap ``initialize`` +the way posthog-js does — mcp 1.x answers it inside ``ServerSession`` and mcp 2.x +refuses a handler for it — so these tests drive a real streamable-HTTP app over +raw JSON-RPC and count what comes out. Written once, collected under both SDK +majors; the version-specific code is :func:`make_pod`, which builds each server +shape a host might run. + +The shapes are the ones agents produce in the field: a client that reconnects +before every few calls (Codex, hosted connectors), a probe that handshakes and +leaves, and a stateless fleet behind a load balancer with no stickiness, where +one handshake's requests land on several pods. +""" + +from __future__ import annotations + +import asyncio +import json +import os +import sys +from contextlib import AsyncExitStack, asynccontextmanager +from typing import Any, Dict, List, Optional + +import httpx +import pytest + +from posthog.mcp import ( + MCPAnalyticsOptions, + UserIdentity, + decode_session_id, + get_request_headers, + instrument, +) +from posthog.test.mcp._helpers import ( + MCP_MAJOR, + FakeClient, + events_named as _events, + flush_background as _flush, +) + +PROTOCOL_VERSION = "2025-06-18" +BASE_HEADERS = { + "content-type": "application/json", + "accept": "application/json, text/event-stream", +} + + +#: The server shapes a host builds, each served the way that shape is served: +#: the official SDK's high-level server, standalone ``fastmcp`` (jlowin's 2.x/3.x +#: on mcp 1.x, 4.x on mcp 2.x), and a low-level ``Server``. +KINDS = ["official", "fastmcp", "lowlevel"] + + +def make_pod( + client: FakeClient, + *, + kind: str = "official", + stateless: bool = True, + options: Optional[MCPAnalyticsOptions] = None, + bind: bool = True, +): + """One instrumented server of ``kind``, as ``(asgi_app, lifespan, server)``. + + ``bind`` only matters for an mcp 1.x low-level server: nothing autowires it, + so the middleware is mounted by hand, with or without ``server=``.""" + options = options or MCPAnalyticsOptions(enable_conversation_id=False) + if kind == "fastmcp": + return _fastmcp_pod(client, options, stateless) + if kind == "lowlevel": + return _lowlevel_pod(client, options, stateless, bind) + if MCP_MAJOR < 2: + from mcp.server.fastmcp import FastMCP + from mcp.server.transport_security import TransportSecuritySettings + + server = FastMCP( + "handshake", + stateless_http=stateless, + json_response=True, + transport_security=TransportSecuritySettings( + enable_dns_rebinding_protection=False + ), + ) + else: + from mcp.server.mcpserver import MCPServer + + server = MCPServer("handshake") + + @server.tool() + def add(a: int, b: int) -> int: + return a + b + + instrument(server, client, options) + if MCP_MAJOR < 2: + app = server.streamable_http_app() + else: + app = server.streamable_http_app(json_response=True, stateless_http=stateless) + return app, server.session_manager.run(), server + + +def _fastmcp_pod(client, options, stateless): + fastmcp = pytest.importorskip("fastmcp") + + server = fastmcp.FastMCP("handshake") + + @server.tool() + def add(a: int, b: int) -> int: + return a + b + + instrument(server, client, options) + app = server.http_app(json_response=True, stateless_http=stateless) + return app, app.router.lifespan_context(app), server + + +def _lowlevel_pod(client, options, stateless, bind): + import mcp.types as mcp_types + from mcp.server.lowlevel import Server + + schema = { + "type": "object", + "properties": {"a": {"type": "integer"}, "b": {"type": "integer"}}, + } + + def total(arguments): + return str((arguments or {}).get("a", 0) + (arguments or {}).get("b", 0)) + + if MCP_MAJOR >= 2: + + async def on_list_tools(ctx, params): + return mcp_types.ListToolsResult( + tools=[mcp_types.Tool(name="add", input_schema=schema)] + ) + + async def on_call_tool(ctx, params): + return mcp_types.CallToolResult( + content=[ + mcp_types.TextContent(type="text", text=total(params.arguments)) + ] + ) + + server = Server( + "handshake", on_list_tools=on_list_tools, on_call_tool=on_call_tool + ) + instrument(server, client, options) + app = server.streamable_http_app(json_response=True, stateless_http=stateless) + return app, server.session_manager.run(), server + + from mcp.server.streamable_http_manager import StreamableHTTPSessionManager + + from posthog.mcp import PostHogMcpStatelessSessionMiddleware + + server = Server("handshake") + + @server.list_tools() + async def list_tools_handler(): + return [mcp_types.Tool(name="add", inputSchema=schema)] + + @server.call_tool() + async def call_tool_handler(name, arguments): + return [mcp_types.TextContent(type="text", text=total(arguments))] + + instrument(server, client, options) + manager = StreamableHTTPSessionManager( + app=server, stateless=stateless, json_response=True + ) + app = ( + PostHogMcpStatelessSessionMiddleware(manager.handle_request, server=server) + if bind + else PostHogMcpStatelessSessionMiddleware(manager.handle_request) + ) + return app, manager.run(), server + + +@asynccontextmanager +async def fleet(client: FakeClient, pods: int = 1, **pod_options: Any): + """``pods`` independently instrumented servers, each a live HTTP surface. Every + pod has its own tracking state, exactly like separate processes behind a + load balancer.""" + async with AsyncExitStack() as stack: + surfaces: List[httpx.AsyncClient] = [] + for _ in range(pods): + app, lifespan, _ = make_pod(client, **pod_options) + await stack.enter_async_context(lifespan) + surfaces.append( + await stack.enter_async_context( + httpx.AsyncClient( + transport=httpx.ASGITransport(app=app), + base_url="http://127.0.0.1:8000", + ) + ) + ) + yield RoundRobin(surfaces) + + +class RoundRobin: + """A load balancer with no stickiness: each request goes to the next pod.""" + + def __init__(self, pods: List[httpx.AsyncClient]): + self.pods = pods + self.turn = 0 + + async def post( + self, body: Dict[str, Any], headers: Dict[str, str] + ) -> httpx.Response: + pod = self.pods[self.turn % len(self.pods)] + self.turn += 1 + return await pod.post("/mcp", json=body, headers=headers) + + +def rpc(method: str, params: Dict[str, Any], request_id: Optional[int] = 1): + body: Dict[str, Any] = {"jsonrpc": "2.0", "method": method, "params": params} + if request_id is not None: + body["id"] = request_id + return body + + +def initialize_params(client_name: str) -> Dict[str, Any]: + return { + "protocolVersion": PROTOCOL_VERSION, + "capabilities": {}, + "clientInfo": {"name": client_name, "version": "0.50.0"}, + } + + +async def handshake( + lb: RoundRobin, + *, + client_name: str = "codex-mcp-client", + extra_headers: Optional[Dict[str, str]] = None, + send_initialized: bool = True, +): + """``initialize`` then ``notifications/initialized``, as every MCP client + connects. Returns the headers the client replays afterwards and the + negotiated protocol version.""" + headers = {**BASE_HEADERS, **(extra_headers or {})} + response = await lb.post(rpc("initialize", initialize_params(client_name)), headers) + assert response.status_code == 200, response.text + negotiated = response.json()["result"]["protocolVersion"] + replay = {**headers, "mcp-protocol-version": negotiated} + session_header = response.headers.get("mcp-session-id") + if session_header: + replay["mcp-session-id"] = session_header + if send_initialized: + await lb.post(rpc("notifications/initialized", {}, None), replay) + return replay, negotiated + + +async def call_add( + lb: RoundRobin, headers: Dict[str, str], request_id: int = 3, **extra: Any +): + """``tools/call add``. ``extra`` adds arguments or params (``_meta``).""" + meta = {"_meta": extra.pop("_meta")} if "_meta" in extra else {} + arguments = {"a": 2, "b": 3, "context": "adding", **extra} + response = await lb.post( + rpc("tools/call", {"name": "add", "arguments": arguments, **meta}, request_id), + headers, + ) + assert response.status_code == 200, response.text + return response + + +async def list_tools(lb: RoundRobin, headers: Dict[str, str]): + response = await lb.post(rpc("tools/list", {}, 2), headers) + assert response.status_code == 200, response.text + return response + + +# --- one event per handshake ------------------------------------------------------ + + +@pytest.mark.parametrize("kind", KINDS) +async def test_one_initialize_per_handshake_across_stateless_pods(kind): + # The overcount this file exists for: dedupe used to be per process, so every + # pod a session touched emitted its own $mcp_initialize. + client = FakeClient() + async with fleet(client, pods=2, kind=kind) as lb: + headers, negotiated = await handshake(lb) + await list_tools(lb, headers) + await call_add(lb, headers, 3) + await call_add(lb, headers, 4) + await call_add(lb, headers, 5) + await _flush() + + inits = _events(client, "$mcp_initialize") + assert len(inits) == 1 + calls = _events(client, "$mcp_tool_call") + assert len(calls) == 3 + sessions = {c["properties"]["$session_id"] for c in calls} + assert sessions == {inits[0]["properties"]["$session_id"]} + props = inits[0]["properties"] + assert props["$mcp_client_name"] == "codex-mcp-client" + assert props["$mcp_client_version"] == "0.50.0" + assert props["$mcp_protocol_version"] == negotiated + assert ( + props["$mcp_parameters"]["params"]["clientInfo"]["name"] == "codex-mcp-client" + ) + assert props["$mcp_duration_ms"] >= 0 + + +@pytest.mark.parametrize("kind", KINDS) +async def test_each_reconnect_is_one_initialize(kind): + client = FakeClient() + async with fleet(client, pods=2, kind=kind) as lb: + for _ in range(3): + headers, _ = await handshake(lb) + await list_tools(lb, headers) + await call_add(lb, headers) + await call_add(lb, headers, 4) + await _flush() + + inits = _events(client, "$mcp_initialize") + assert len(inits) == 3 + assert len({e["properties"]["$session_id"] for e in inits}) == 3 + assert len(_events(client, "$mcp_tool_call")) == 6 + + +@pytest.mark.parametrize("kind", KINDS) +async def test_a_bare_handshake_is_counted(kind): + # Probes and health checkers connect and leave. There is no later request to + # hang a synthesized event on, which used to make them invisible. + client = FakeClient() + async with fleet(client, kind=kind) as lb: + await handshake(lb, send_initialized=False) + await handshake(lb) + await _flush() + + assert len(_events(client, "$mcp_initialize")) == 2 + + +@pytest.mark.parametrize("kind", KINDS) +async def test_initialize_session_matches_a_stateful_transport(kind): + # On a stateful server the transport assigns Mcp-Session-Id in the initialize + # response; the event must land in the session every later request resolves to. + client = FakeClient() + async with fleet(client, stateless=False, kind=kind) as lb: + headers, _ = await handshake(lb) + await call_add(lb, headers) + await call_add(lb, headers, 4) + await _flush() + + inits = _events(client, "$mcp_initialize") + assert len(inits) == 1 + calls = _events(client, "$mcp_tool_call") + assert {c["properties"]["$session_id"] for c in calls} == { + inits[0]["properties"]["$session_id"] + } + + +async def test_initialize_is_attributed_on_the_pod_that_served_it(): + def identify(request, extra): + headers = get_request_headers(extra) or {} + token = headers.get("authorization", "") + return ( + UserIdentity(distinct_id=token.removeprefix("Bearer ")) if token else None + ) + + client = FakeClient() + options = MCPAnalyticsOptions(enable_conversation_id=False, identify=identify) + async with fleet(client, pods=2, options=options) as lb: + headers, _ = await handshake( + lb, extra_headers={"authorization": "Bearer user_alice"} + ) + await call_add(lb, headers) + await _flush() + + (init,) = _events(client, "$mcp_initialize") + assert init["distinct_id"] == "user_alice" + assert init["properties"]["$mcp_client_user_agent"] + + +@pytest.mark.skipif( + MCP_MAJOR < 2, + reason="mcp 1.x reads only the response head, and a JSON-RPC error is an HTTP 200", +) +async def test_a_failed_initialize_is_not_captured(): + client = FakeClient() + async with fleet(client) as lb: + response = await lb.post(rpc("initialize", {"capabilities": {}}), BASE_HEADERS) + await _flush() + + assert "error" in response.json() + assert _events(client, "$mcp_initialize") == [] + + +async def test_an_echoed_conversation_does_not_initialize_again(): + # ADR-0004 anchors echoed calls to the conversation's session. That session + # never sent an initialize, so it gets no $mcp_initialize of its own. + from posthog.mcp._conversation_id import _uuid7 + + conversation_id = _uuid7() + client = FakeClient() + options = MCPAnalyticsOptions(enable_conversation_id=True) + async with fleet(client, pods=2, options=options) as lb: + headers, _ = await handshake(lb) + for request_id in (3, 4, 5): + await call_add(lb, headers, request_id, conversation_id=conversation_id) + await _flush() + + calls = _events(client, "$mcp_tool_call") + assert len(calls) == 3 + assert {c["properties"]["$mcp_conversation_id"] for c in calls} == {conversation_id} + assert len(_events(client, "$mcp_initialize")) == 1 + + +# --- the 2026-07-28 revision has no initialize ------------------------------------- + + +@pytest.mark.skipif(MCP_MAJOR < 2, reason="mcp 1.x cannot serve 2026-07-28") +async def test_modern_era_requests_emit_no_initialize(): + from posthog.test.mcp._helpers_v2 import modern_headers, modern_meta + + client = FakeClient() + headers = {**BASE_HEADERS, **modern_headers("tools/call", "add")} + async with fleet(client, pods=2) as lb: + for request_id in (1, 2, 3): + await call_add(lb, headers, request_id, _meta=modern_meta()) + await _flush() + + assert len(_events(client, "$mcp_tool_call")) == 3 + assert _events(client, "$mcp_initialize") == [] + + +# --- mcp 1.x low-level server, middleware mounted by hand -------------------------- + + +@pytest.mark.skipif(MCP_MAJOR >= 2, reason="mcp 2.x observes initialize in-process") +async def test_an_unbound_middleware_keeps_the_first_request_fallback(): + # Without server= the middleware cannot reach the tracking state, so the first + # request of the session on each pod still stands in for the handshake: the + # pre-existing behaviour, kept so a hand-mounted middleware never goes dark. + client = FakeClient() + async with fleet(client, pods=2, kind="lowlevel", bind=False) as lb: + headers, _ = await handshake(lb) + await call_add(lb, headers) + await call_add(lb, headers, 4) + await _flush() + + assert len(_events(client, "$mcp_initialize")) == 2 + + +# --- stdio: a real server process, driven over its pipes --------------------------- + +_STDIO_SERVER = """ +import importlib.metadata +import json +import os + +from posthog.mcp import MCPAnalyticsOptions, instrument + + +class FileClient: + # Appends each capture to a file the test reads, one JSON line per event. + def capture(self, event, distinct_id=None, properties=None, **kwargs): + with open(os.environ["EVENTS_FILE"], "a") as sink: + sink.write(json.dumps({"event": event, "properties": properties or {}}, default=str) + "\\n") + + +if int(importlib.metadata.version("mcp").split(".")[0]) < 2: + from mcp.server.fastmcp import FastMCP as Server +else: + from mcp.server.mcpserver import MCPServer as Server + +server = Server("stdio-handshake") + + +@server.tool() +def add(a: int, b: int) -> int: + return a + b + + +instrument(server, FileClient(), MCPAnalyticsOptions(enable_conversation_id=False)) +server.run() +""" + + +class StdioServer: + """The instrumented server as a child process on stdio, the way a desktop client + launches it: newline-delimited JSON-RPC on stdin/stdout.""" + + def __init__(self, process, events_file): + self.process = process + self.events_file = events_file + + async def send(self, method, params, request_id=None): + self.process.stdin.write( + (json.dumps(rpc(method, params, request_id)) + "\n").encode() + ) + await self.process.stdin.drain() + if request_id is None: + return None + line = await asyncio.wait_for(self.process.stdout.readline(), timeout=20) + return json.loads(line) + + async def events(self, name, count): + """Captures are fire-and-forget in the child: wait for ``count`` of them, + then a moment more for any that would make it one too many.""" + for _ in range(100): + if self.events_file.exists(): + lines = self.events_file.read_text().splitlines() + found = [e for e in map(json.loads, lines) if e["event"] == name] + if len(found) >= count: + break + await asyncio.sleep(0.05) + await asyncio.sleep(0.2) + lines = self.events_file.read_text().splitlines() + return [e for e in map(json.loads, lines) if e["event"] == name] + + +@asynccontextmanager +async def stdio_server(tmp_path): + script = tmp_path / "stdio_server.py" + script.write_text(_STDIO_SERVER) + events_file = tmp_path / "events.jsonl" + process = await asyncio.create_subprocess_exec( + sys.executable, + str(script), + stdin=asyncio.subprocess.PIPE, + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.DEVNULL, + env={**os.environ, "EVENTS_FILE": str(events_file)}, + cwd=str(tmp_path), + ) + try: + yield StdioServer(process, events_file) + finally: + if process.stdin and not process.stdin.is_closing(): + process.stdin.close() + try: + await asyncio.wait_for(process.wait(), timeout=10) + except asyncio.TimeoutError: + process.kill() + await process.wait() + + +async def test_stdio_handshake_then_calls(tmp_path): + async with stdio_server(tmp_path) as server: + reply = await server.send("initialize", initialize_params("claude-desktop"), 1) + assert "result" in reply, reply + await server.send("notifications/initialized", {}) + await server.send("tools/list", {}, 2) + for request_id in (3, 4): + reply = await server.send( + "tools/call", + {"name": "add", "arguments": {"a": 1, "b": 2, "context": "stdio"}}, + request_id, + ) + assert reply["result"]["isError"] is False, reply + calls = await server.events("$mcp_tool_call", 2) + inits = await server.events("$mcp_initialize", 1) + + assert len(calls) == 2 + assert len(inits) == 1 + props = inits[0]["properties"] + assert props["$mcp_client_name"] == "claude-desktop" + assert {c["properties"]["$session_id"] for c in calls} == {props["$session_id"]} + + +@pytest.mark.skipif(MCP_MAJOR < 2, reason="mcp 1.x stdio has no handshake seam") +async def test_stdio_bare_handshake_is_counted(tmp_path): + async with stdio_server(tmp_path) as server: + reply = await server.send("initialize", initialize_params("claude-desktop"), 1) + assert "result" in reply, reply + inits = await server.events("$mcp_initialize", 1) + + assert len(inits) == 1 + assert ( + inits[0]["properties"]["$mcp_protocol_version"] + == reply["result"]["protocolVersion"] + ) + + +# --- custom dispatcher: PostHogMCP behind an unbound middleware --------------------- + + +def make_dispatcher(posthog_mcp): + """A hand-rolled JSON-RPC endpoint, the shape ``PostHogMCP`` exists for: the host + answers ``initialize`` itself and captures it through the manual API.""" + from starlette.applications import Starlette + from starlette.responses import JSONResponse, Response + from starlette.routing import Route + + from posthog.mcp import get_mcp_session + + async def endpoint(request): + message = await request.json() + if "id" not in message: # a notification: accepted, nothing to answer + return Response(status_code=202) + result: Dict[str, Any] = {"tools": []} + if message["method"] == "initialize": + session = get_mcp_session(request) + posthog_mcp.capture_initialize( + client_name=message["params"]["clientInfo"]["name"], + session_id=session.session_id if session else None, + ) + result = { + "protocolVersion": message["params"]["protocolVersion"], + "capabilities": {}, + "serverInfo": {"name": "dispatcher", "version": "1.0.0"}, + } + return JSONResponse({"jsonrpc": "2.0", "id": message["id"], "result": result}) + + return Starlette(routes=[Route("/mcp", endpoint, methods=["POST"])]) + + +async def test_a_posthogmcp_dispatcher_reports_only_its_own_initialize(): + from posthog.mcp import PostHogMCP, PostHogMcpStatelessSessionMiddleware + + captured: List[Dict[str, Any]] = [] + + def keep(event): + captured.append(event) + return event + + posthog_mcp = PostHogMCP("phc_test", send=False, before_send=keep) + # An instrumented server in the same process: an unbound middleware must never + # attach the dispatcher's handshakes to it. + bystander = FakeClient() + make_pod(bystander) + + app = PostHogMcpStatelessSessionMiddleware(make_dispatcher(posthog_mcp)) + async with httpx.AsyncClient( + transport=httpx.ASGITransport(app=app), base_url="http://127.0.0.1:8000" + ) as http: + lb = RoundRobin([http]) + headers, _ = await handshake(lb, client_name="dispatcher-client") + await list_tools(lb, headers) + await _flush() + + inits = [e for e in captured if e["event"] == "$mcp_initialize"] + assert len(inits) == 1 + assert inits[0]["properties"]["$mcp_client_name"] == "dispatcher-client" + # The session the host read back from the minted token, not a second one. + token = decode_session_id(headers["mcp-session-id"]) + assert token is not None + assert inits[0]["properties"]["$session_id"] == token.session_id + assert bystander.events == [] + + +# --- the deprecated SSE transport: session in a query parameter --------------------- + + +@asynccontextmanager +async def sse_server(client: FakeClient): + """An instrumented server's ``sse_app()`` on a real port. The SSE transport keeps + its session in the ``session_id`` query parameter of ``/messages/``, not in a + header, and answers each POST with 202 and the reply on the event stream.""" + import uvicorn + + if MCP_MAJOR < 2: + from mcp.server.fastmcp import FastMCP + from mcp.server.transport_security import TransportSecuritySettings + + server = FastMCP( + "handshake", + transport_security=TransportSecuritySettings( + enable_dns_rebinding_protection=False + ), + ) + else: + from mcp.server.mcpserver import MCPServer + + server = MCPServer("handshake") + + @server.tool() + def add(a: int, b: int) -> int: + return a + b + + instrument(server, client, MCPAnalyticsOptions(enable_conversation_id=False)) + config = uvicorn.Config(server.sse_app(), port=0, log_level="error", ws="none") + http_server = uvicorn.Server(config) + serving = asyncio.create_task(http_server.serve()) + while not http_server.started: + await asyncio.sleep(0.01) + port = http_server.servers[0].sockets[0].getsockname()[1] + try: + yield f"http://127.0.0.1:{port}" + finally: + http_server.should_exit = True + await asyncio.wait_for(serving, timeout=10) + + +async def test_sse_transport_handshake_then_calls(): + client = FakeClient() + async with sse_server(client) as url: + async with httpx.AsyncClient(base_url=url, timeout=20) as http: + async with http.stream("GET", "/sse") as stream: + lines = stream.aiter_lines() + + async def next_data() -> str: + async for line in lines: + if line.startswith("data:"): + return line[len("data:") :].strip() + raise AssertionError("the event stream closed") + + endpoint = await next_data() + + async def send(method, params, request_id=None): + response = await http.post( + endpoint, json=rpc(method, params, request_id) + ) + assert response.status_code == 202, response.text + return json.loads(await next_data()) if request_id else None + + await send("initialize", initialize_params("sse-client"), 1) + await send("notifications/initialized", {}) + await send("tools/list", {}, 2) + for request_id in (3, 4): + arguments = {"a": 1, "b": 2, "context": "sse"} + reply = await send( + "tools/call", + {"name": "add", "arguments": arguments}, + request_id, + ) + assert reply["result"]["isError"] is False, reply + await _flush() + + (init,) = _events(client, "$mcp_initialize") + calls = _events(client, "$mcp_tool_call") + assert len(calls) == 2 + assert {c["properties"]["$session_id"] for c in calls} == { + init["properties"]["$session_id"] + } + + +# --- the middleware is per request and fail-safe ----------------------------------- + + +@pytest.mark.skipif(MCP_MAJOR >= 2, reason="mcp 2.x has no first-request fallback") +async def test_a_route_without_the_middleware_keeps_the_fallback(): + # One process, one server, two routes: the autowired app with the bound + # middleware, and the session manager mounted by hand without it. Each route + # reports its own handshakes, once. + client = FakeClient() + app, lifespan, server = make_pod(client) + async with lifespan: + for surface in (app, server.session_manager.handle_request): + async with httpx.AsyncClient( + transport=httpx.ASGITransport(app=surface), + base_url="http://127.0.0.1:8000", + ) as http: + lb = RoundRobin([http]) + headers, _ = await handshake(lb) + await call_add(lb, headers) + await call_add(lb, headers, 4) + await _flush() + + inits = _events(client, "$mcp_initialize") + assert len(inits) == 2 + calls = _events(client, "$mcp_tool_call") + assert {c["properties"]["$session_id"] for c in calls} == { + e["properties"]["$session_id"] for e in inits + } + + +async def test_a_handshake_leaves_the_shared_session_alone(): + # McpAnalytics.capture() reads the server's shared session. A handshake that + # carries its own session (a token here) must not move it. + from posthog.mcp._internal import get_server_tracking_data, tracking_key + + client = FakeClient() + app, lifespan, server = make_pod(client) + data = get_server_tracking_data(tracking_key(server)) + before = (data.session_id, data.session_source) + async with lifespan: + async with httpx.AsyncClient( + transport=httpx.ASGITransport(app=app), base_url="http://127.0.0.1:8000" + ) as http: + await handshake(RoundRobin([http])) + await _flush() + + assert len(_events(client, "$mcp_initialize")) == 1 + assert (data.session_id, data.session_source) == before + + +async def test_a_server_that_cannot_be_bound_does_not_break_requests(): + from posthog.mcp import PostHogMcpStatelessSessionMiddleware + + async def echo(scope, receive, send): + await send({"type": "http.response.start", "status": 200, "headers": []}) + await send({"type": "http.response.body", "body": b"{}"}) + + for unbindable in (object(), {"not": "hashable"}): + app = PostHogMcpStatelessSessionMiddleware(echo, server=unbindable) + async with httpx.AsyncClient( + transport=httpx.ASGITransport(app=app), base_url="http://127.0.0.1:8000" + ) as http: + response = await http.post( + "/mcp", + json=rpc("initialize", initialize_params("x")), + headers=BASE_HEADERS, + ) + assert response.status_code == 200 + + +@pytest.mark.parametrize("kind", KINDS) +async def test_flush_waits_for_the_handshake_capture(kind): + # The handshake capture starts during the request. flush() must wait for it, + # or shutdown drops $mcp_initialize when identify or before_send is slow. + from posthog.mcp import McpAnalytics + from posthog.mcp._internal import tracking_key + + async def slow_identify(request, extra): + await asyncio.sleep(0.05) + return UserIdentity(distinct_id="user_slow") + + async def slow_before_send(event): + await asyncio.sleep(0.05) + return event + + client = FakeClient() + options = MCPAnalyticsOptions( + enable_conversation_id=False, + identify=slow_identify, + before_send=slow_before_send, + ) + app, lifespan, server = make_pod(client, kind=kind, options=options) + async with lifespan: + async with httpx.AsyncClient( + transport=httpx.ASGITransport(app=app), base_url="http://127.0.0.1:8000" + ) as http: + await handshake(RoundRobin([http]), send_initialized=False) + await McpAnalytics(tracking_key(server)).flush() + assert len(_events(client, "$mcp_initialize")) == 1 + + +@pytest.mark.skipif( + MCP_MAJOR < 2, + reason="mcp 1.x does not read a request that carries a session id (README)", +) +@pytest.mark.parametrize("kind", KINDS) +async def test_an_initialize_with_a_session_header_keeps_that_session(kind): + # A stateless client that sends initialize with an existing Mcp-Session-Id + # keeps sending it, and its later requests resolve their session from it. + client = FakeClient() + async with fleet(client, pods=2, kind=kind) as lb: + headers, _ = await handshake( + lb, extra_headers={"mcp-session-id": "an-existing-session"} + ) + assert headers["mcp-session-id"] == "an-existing-session" + await call_add(lb, headers) + await call_add(lb, headers, 4) + await _flush() + + (init,) = _events(client, "$mcp_initialize") + calls = _events(client, "$mcp_tool_call") + assert {c["properties"]["$session_id"] for c in calls} == { + init["properties"]["$session_id"] + } + + +@pytest.mark.parametrize("kind", KINDS) +async def test_a_hung_identify_leaves_no_task_behind(kind): + # identify runs inline in the handshake, as it does on a tool call. A callback + # that never returns holds those requests, and leaves no analytics task behind + # for each connection. Once the callback recovers, recording resumes. + import contextlib + + import posthog.mcp._instrumentation as instr + from posthog.mcp import McpAnalytics + from posthog.mcp._internal import get_server_tracking_data, tracking_key + + recovered = asyncio.Event() + + async def identify(request, extra): + await recovered.wait() + return UserIdentity(distinct_id="user_1") + + client = FakeClient() + options = MCPAnalyticsOptions(enable_conversation_id=False, identify=identify) + app, lifespan, server = make_pod(client, kind=kind, options=options) + data = get_server_tracking_data(tracking_key(server)) + + def pending(): + with instr._tasks_lock: + tasks = list(instr._BACKGROUND_TASKS.items()) + return [task for task, owner in tasks if owner is data and not task.done()] + + async with lifespan: + async with httpx.AsyncClient( + transport=httpx.ASGITransport(app=app), base_url="http://127.0.0.1:8000" + ) as http: + lb = RoundRobin([http]) + for _ in range(5): # each client gives up, as a timeout would + with contextlib.suppress(asyncio.TimeoutError): + await asyncio.wait_for(handshake(lb, send_initialized=False), 0.2) + assert pending() == [] + await asyncio.wait_for(McpAnalytics(tracking_key(server)).flush(), 1) + + recovered.set() + await handshake(lb, client_name="after-recovery", send_initialized=False) + await _flush() + + names = [ + e["properties"]["$mcp_client_name"] for e in _events(client, "$mcp_initialize") + ] + assert "after-recovery" in names + + +@pytest.mark.skipif(MCP_MAJOR >= 2, reason="the mcp 1.x auth context middleware") +async def test_identify_sees_the_auth_context_of_the_handshake(): + # FastMCP's AuthContextMiddleware sets the authenticated user for the length of + # the request and resets it on the way out. Our middleware sits outside it, so + # the handshake must be recorded while the app still handles the request. + from mcp.server.auth.middleware.auth_context import ( + auth_context_var, + get_access_token, + ) + from mcp.server.auth.middleware.bearer_auth import AuthenticatedUser + from mcp.server.auth.provider import AccessToken + + from posthog.mcp import PostHogMcpStatelessSessionMiddleware + + def with_user(app): + user = AuthenticatedUser( + AccessToken(token="t", client_id="alice", scopes=[], expires_at=None) + ) + + async def authenticated(scope, receive, send): + reset = auth_context_var.set(user) + try: + await app(scope, receive, send) + finally: + auth_context_var.reset(reset) + + return authenticated + + def identify(request, extra): + token = get_access_token() + return UserIdentity(distinct_id=token.client_id) if token else None + + client = FakeClient() + options = MCPAnalyticsOptions(enable_conversation_id=False, identify=identify) + _, lifespan, server = make_pod(client, options=options) + app = PostHogMcpStatelessSessionMiddleware( + with_user(server.session_manager.handle_request), server=server + ) + async with lifespan: + async with httpx.AsyncClient( + transport=httpx.ASGITransport(app=app), base_url="http://127.0.0.1:8000" + ) as http: + await handshake(RoundRobin([http]), send_initialized=False) + await _flush() + + (init,) = _events(client, "$mcp_initialize") + assert init["distinct_id"] == "alice" + + +@pytest.mark.parametrize("kind", KINDS) +async def test_an_escaped_initialize_still_mints_a_session(kind): + # "\u0069nitialize" is valid JSON for "initialize". The middleware parses the + # body as JSON, so it mints the token and captures the handshake. + raw = ( + b'{"jsonrpc":"2.0","id":1,"method":"\\u0069nitialize","params":' + + json.dumps(initialize_params("escaped-client")).encode() + + b"}" + ) + client = FakeClient() + async with fleet(client, kind=kind) as lb: + response = await lb.pods[0].post("/mcp", content=raw, headers=BASE_HEADERS) + await _flush() + + assert response.status_code == 200, response.text + assert decode_session_id(response.headers.get("mcp-session-id")) is not None + (init,) = _events(client, "$mcp_initialize") + assert init["properties"]["$mcp_client_name"] == "escaped-client" + + +@pytest.mark.skipif( + MCP_MAJOR < 2, + reason="mcp 1.x does not read a request that carries a session id (README)", +) +@pytest.mark.parametrize("kind", KINDS) +async def test_a_nested_method_before_the_top_level_initialize_is_counted(kind): + # JSON key order is arbitrary: params can come first, and can hold their own + # "method" key. Only the top-level method says whether this is a handshake. + params = initialize_params("nested-client") + params["capabilities"] = { + "experimental": {"vendor": {"method": "custom", "padding": "x" * 8192}} + } + body = {"params": params, "jsonrpc": "2.0", "id": 1, "method": "initialize"} + headers = {**BASE_HEADERS, "mcp-session-id": "an-existing-session"} + client = FakeClient() + async with fleet(client, kind=kind) as lb: + response = await lb.post(body, headers) + await _flush() + + assert response.status_code == 200, response.text + (init,) = _events(client, "$mcp_initialize") + assert init["properties"]["$mcp_client_name"] == "nested-client" diff --git a/posthog/test/mcp/test_session_token.py b/posthog/test/mcp/test_session_token.py index 4cb0ac81..9db5aca7 100644 --- a/posthog/test/mcp/test_session_token.py +++ b/posthog/test/mcp/test_session_token.py @@ -418,6 +418,36 @@ async def send(message): assert MCP_SESSION_HEADER not in sent["headers"] # too big to sniff -> no mint +async def test_middleware_passes_a_disconnect_mid_body_to_the_app(): + """A client that disconnects while it sends the body must reach the app as a + disconnect, not as a complete request made of the part that arrived.""" + messages = [ + {"type": "http.request", "body": b'{"jsonrpc":"2.0","met', "more_body": True}, + {"type": "http.disconnect"}, + ] + seen = [] + + async def app(scope, receive, send): + while True: + message = await receive() + seen.append(message) + if message["type"] != "http.request" or not message.get("more_body"): + return + + async def receive(): + return messages.pop(0) if messages else {"type": "http.disconnect"} + + async def send(message): + pass + + await PostHogMcpStatelessSessionMiddleware(app)(_scope(), receive, send) + + assert seen == [ + {"type": "http.request", "body": b'{"jsonrpc":"2.0","met', "more_body": True}, + {"type": "http.disconnect"}, + ] + + # --- end-to-end against a real stateless FastMCP transport ------------------- diff --git a/posthog/test/mcp/test_v2_lowlevel.py b/posthog/test/mcp/test_v2_lowlevel.py index 905292b1..319cbc3e 100644 --- a/posthog/test/mcp/test_v2_lowlevel.py +++ b/posthog/test/mcp/test_v2_lowlevel.py @@ -481,7 +481,7 @@ async def late_read_resource(ctx, params): assert reads[0]["properties"]["$mcp_resource_name"] == "file:///late.txt" -async def test_initialize_and_session_reuse_across_calls(): +async def test_session_reuse_across_calls_without_a_handshake(): server = make_server() client = FakeClient() instrument(server, client, MCPAnalyticsOptions(enable_conversation_id=False)) @@ -490,7 +490,9 @@ async def test_initialize_and_session_reuse_across_calls(): await _call_tool(server, "add", {"a": 2, "b": 2, "context": "second"}) await _flush() - assert len(_events(client, "$mcp_initialize")) == 1 + # No initialize was sent, so none is reported: Server.middleware sees every + # real handshake (test_initialize_handshake.py). + assert _events(client, "$mcp_initialize") == [] calls = _events(client, "$mcp_tool_call") session_ids = {c["properties"]["$session_id"] for c in calls} assert len(session_ids) == 1 diff --git a/posthog/test/mcp/test_v2_mcpserver.py b/posthog/test/mcp/test_v2_mcpserver.py index 0525da3e..5b3ba1cb 100644 --- a/posthog/test/mcp/test_v2_mcpserver.py +++ b/posthog/test/mcp/test_v2_mcpserver.py @@ -195,7 +195,7 @@ async def test_tool_call_error_is_captured_and_converted(): assert exception_list[-1]["value"] == "explode" -async def test_initialize_emitted_once_per_session(): +async def test_tool_calls_alone_emit_no_initialize(): server = make_server() client = FakeClient() instrument(server, client, MCPAnalyticsOptions(enable_conversation_id=False)) @@ -208,7 +208,9 @@ async def test_initialize_emitted_once_per_session(): ) await _flush() - assert len(_events(client, "$mcp_initialize")) == 1 + # No initialize was sent, so none is reported: Server.middleware sees every + # real handshake (test_initialize_handshake.py). + assert _events(client, "$mcp_initialize") == [] assert len(_events(client, "$mcp_tool_call")) == 2 diff --git a/references/public_api_snapshot.txt b/references/public_api_snapshot.txt index bfed0cfe..e5245a7e 100644 --- a/references/public_api_snapshot.txt +++ b/references/public_api_snapshot.txt @@ -351,6 +351,7 @@ alias posthog.mcp.asgi.MCP_SESSION_HEADER -> posthog.mcp.session_token.MCP_SESSI alias posthog.mcp.asgi.SessionTokenPayload -> posthog.mcp.session_token.SessionTokenPayload alias posthog.mcp.asgi.decode_session_id -> posthog.mcp.session_token.decode_session_id alias posthog.mcp.asgi.encode_session_id -> posthog.mcp.session_token.encode_session_id +alias posthog.mcp.asgi.handshake_session_id -> posthog.mcp.session.handshake_session_id alias posthog.mcp.asgi.log -> posthog.mcp.logger.log alias posthog.mcp.asgi.new_session_id -> posthog.mcp.session.new_session_id alias posthog.mcp.asgi.read_mcp_session_header -> posthog.mcp.session_token.read_mcp_session_header @@ -1287,7 +1288,7 @@ class posthog.integrations.django.PosthogContextMiddleware(get_response) class posthog.integrations.flask.PosthogFlaskIntegration(app: Optional[Flask] = None, *, client: Optional[Client] = None, capture_exceptions: Optional[bool] = None, request_filter: Optional[Callable[[Request], bool]] = None, extra_properties: Optional[Callable[[Request], Mapping[str, Any]]] = None, trust_tracing_headers: Optional[bool] = None, capture_tracing_headers: Optional[bool] = None) class posthog.integrations.otel.PostHogContextSpanProcessor class posthog.mcp.McpAnalytics(key: Any) -class posthog.mcp.asgi.PostHogMcpStatelessSessionMiddleware(app: Any) +class posthog.mcp.asgi.PostHogMcpStatelessSessionMiddleware(app: Any, server: Any = None) class posthog.mcp.constants.PostHogMCPAnalyticsEvent class posthog.mcp.constants.PostHogMCPAnalyticsProperty class posthog.mcp.posthog_mcp.PostHogMCP(api_key: str, missing_capability_tool_name: Optional[str] = None, mcp_exception_autocapture: bool = True, capture_model: Union[bool, MCPAnalyticsModelOptions] = True, collect_feedback: Union[bool, CollectFeedbackOptions] = False, server_build: Optional[str] = None, enable_conversation_id: bool = True, **kwargs: Any) From 73135d6251694b78b8ebb0dbd8e70b87e65fa17d Mon Sep 17 00:00:00 2001 From: alexcastgonz <82875882+pcbeingused333@users.noreply.github.com> Date: Fri, 23 Oct 2026 08:38:05 -0400 Subject: [PATCH 10/60] fix(ai): take cumulative usage and web searches from the Claude Agent SDK message_delta (#1112) The tracker only read output_tokens from a message_delta. Its usage is cumulative, and with server tools (web search, ...) the input and cache counts exceed the message_start values, so streamed generations under-reported them, and the number of web searches was never recorded. Keep the larger input and cache counts, as the Anthropic wrapper does, and set $ai_web_search_count from server_tool_use.web_search_requests. Co-authored-by: Claude Sonnet 5.5 (cherry picked from commit 48889b6cc8d0a9eec579684640cb74b393cdd56c) --- .../claude-agent-sdk-message-delta-usage.md | 5 ++ posthog/ai/claude_agent_sdk/processor.py | 21 +++++- .../ai/claude_agent_sdk/test_processor.py | 75 +++++++++++++++++++ 3 files changed, 100 insertions(+), 1 deletion(-) create mode 100644 .sampo/changesets/claude-agent-sdk-message-delta-usage.md diff --git a/.sampo/changesets/claude-agent-sdk-message-delta-usage.md b/.sampo/changesets/claude-agent-sdk-message-delta-usage.md new file mode 100644 index 00000000..3db86789 --- /dev/null +++ b/.sampo/changesets/claude-agent-sdk-message-delta-usage.md @@ -0,0 +1,5 @@ +--- +pypi/posthog: patch +--- + +The Claude Agent SDK integration now takes the cumulative input and cache token counts from the final `message_delta` usage, which grow past the `message_start` values when server tools such as web search run, and sets `$ai_web_search_count` from `server_tool_use.web_search_requests`. Streamed generations used to under-report input tokens and cache writes and never recorded the web search count. diff --git a/posthog/ai/claude_agent_sdk/processor.py b/posthog/ai/claude_agent_sdk/processor.py index da0afa5d..8648e9e3 100644 --- a/posthog/ai/claude_agent_sdk/processor.py +++ b/posthog/ai/claude_agent_sdk/processor.py @@ -51,6 +51,7 @@ class _GenerationData: output_tokens: Optional[int] = None cache_read_input_tokens: Optional[int] = None cache_creation_input_tokens: Optional[int] = None + web_search_count: Optional[int] = None raw_usage: Optional[Dict[str, Any]] = None start_time: float = 0.0 end_time: float = 0.0 @@ -92,9 +93,25 @@ def process_stream_event(self, event: "StreamEvent") -> None: elif event_type == "message_delta" and self._current is not None: usage = raw.get("usage", {}) self._current.raw_usage = {**(self._current.raw_usage or {}), **usage} - # message_delta usage reports cumulative output tokens + # message_delta usage is cumulative: output tokens, and with server + # tools (web search, ...) the input and cache counts grow past the + # message_start values. Keep the larger count; a field the delta + # omits keeps the message_start value. if usage.get("output_tokens"): self._current.output_tokens = usage["output_tokens"] + for field_name in ( + "input_tokens", + "cache_read_input_tokens", + "cache_creation_input_tokens", + ): + delta_value = usage.get(field_name) + current_value = getattr(self._current, field_name) + if isinstance(delta_value, int) and delta_value > (current_value or 0): + setattr(self._current, field_name, delta_value) + server_tool_use = usage.get("server_tool_use") or {} + web_search_requests = server_tool_use.get("web_search_requests") + if isinstance(web_search_requests, int) and web_search_requests > 0: + self._current.web_search_count = web_search_requests # Extract stop reason from message_delta delta_stop_reason = raw.get("delta", {}).get("stop_reason") if delta_stop_reason is not None: @@ -445,6 +462,8 @@ def _emit_generation( properties["$ai_cache_creation_input_tokens"] = ( gen.cache_creation_input_tokens ) + if gen.web_search_count: + properties["$ai_web_search_count"] = gen.web_search_count if gen.raw_usage: properties["$ai_usage"] = gen.raw_usage diff --git a/posthog/test/ai/claude_agent_sdk/test_processor.py b/posthog/test/ai/claude_agent_sdk/test_processor.py index 3fae9b68..cfa673df 100644 --- a/posthog/test/ai/claude_agent_sdk/test_processor.py +++ b/posthog/test/ai/claude_agent_sdk/test_processor.py @@ -221,6 +221,81 @@ async def test_emits_generation_from_stream_events(self, processor, mock_client) assert props["$ai_output_tokens"] == 50 assert props["$ai_cache_read_input_tokens"] == 20 + @pytest.mark.asyncio + async def test_message_delta_usage_is_cumulative_with_server_tools( + self, processor, mock_client + ): + # With server tools the final message_delta carries the cumulative input + # and cache counts and the number of web searches. + message_delta = _make_stream_event( + "message_delta", + { + "delta": {"stop_reason": "end_turn"}, + "usage": { + "input_tokens": 5120, + "output_tokens": 300, + "cache_read_input_tokens": 64, + "cache_creation_input_tokens": 32, + "server_tool_use": {"web_search_requests": 2}, + }, + }, + ) + messages = [ + _make_message_start(input_tokens=120, cache_read=0, cache_creation=0), + message_delta, + _make_message_stop(), + _make_assistant_message(), + _make_result_message(), + ] + + with patch( + "posthog.ai.claude_agent_sdk.processor.original_query", + side_effect=lambda **kw: _fake_query(messages), + ): + async for _ in processor.query(prompt="Hi", options=ClaudeAgentOptions()): + pass + + gen_call = next( + c + for c in mock_client.capture_ai.call_args_list + if (c.kwargs.get("event") or c[1].get("event")) == "$ai_generation" + ) + props = gen_call.kwargs.get("properties") or gen_call[1].get("properties") + assert props["$ai_input_tokens"] == 5120 + assert props["$ai_output_tokens"] == 300 + assert props["$ai_cache_read_input_tokens"] == 64 + assert props["$ai_cache_creation_input_tokens"] == 32 + assert props["$ai_web_search_count"] == 2 + + @pytest.mark.asyncio + async def test_message_delta_without_input_counts_keeps_message_start( + self, processor, mock_client + ): + messages = [ + _make_message_start(input_tokens=100, cache_read=20), + _make_message_delta(output_tokens=50), + _make_message_stop(), + _make_assistant_message(), + _make_result_message(), + ] + + with patch( + "posthog.ai.claude_agent_sdk.processor.original_query", + side_effect=lambda **kw: _fake_query(messages), + ): + async for _ in processor.query(prompt="Hi", options=ClaudeAgentOptions()): + pass + + gen_call = next( + c + for c in mock_client.capture_ai.call_args_list + if (c.kwargs.get("event") or c[1].get("event")) == "$ai_generation" + ) + props = gen_call.kwargs.get("properties") or gen_call[1].get("properties") + assert props["$ai_input_tokens"] == 100 + assert props["$ai_cache_read_input_tokens"] == 20 + assert "$ai_web_search_count" not in props + @pytest.mark.asyncio async def test_emits_cache_creation_ttl_breakdown_from_stream_events( self, processor, mock_client From 0281c2c53431261877c38c1b13a6f08f70c91700 Mon Sep 17 00:00:00 2001 From: "releaser-posthog-python[bot]" <262414804+releaser-posthog-python[bot]@users.noreply.github.com> Date: Fri, 23 Oct 2026 12:38:51 +0000 Subject: [PATCH 11/60] chore: Release v7.75.9 [skip ci] (cherry picked from commit 5a2ea5f7e0c71d6b9dc70112ab9d5c29c6f99bb1) --- .../claude-agent-sdk-message-delta-usage.md | 5 ----- .sampo/changesets/mcp-initialize-per-handshake.md | 11 ----------- openfeature-provider/CHANGELOG.md | 6 ++++++ openfeature-provider/pyproject.toml | 2 +- posthog/CHANGELOG.md | 13 +++++++++++++ posthog/version.py | 2 +- pyproject.toml | 2 +- uv.lock | 4 ++-- 8 files changed, 24 insertions(+), 21 deletions(-) delete mode 100644 .sampo/changesets/claude-agent-sdk-message-delta-usage.md delete mode 100644 .sampo/changesets/mcp-initialize-per-handshake.md diff --git a/.sampo/changesets/claude-agent-sdk-message-delta-usage.md b/.sampo/changesets/claude-agent-sdk-message-delta-usage.md deleted file mode 100644 index 3db86789..00000000 --- a/.sampo/changesets/claude-agent-sdk-message-delta-usage.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -pypi/posthog: patch ---- - -The Claude Agent SDK integration now takes the cumulative input and cache token counts from the final `message_delta` usage, which grow past the `message_start` values when server tools such as web search run, and sets `$ai_web_search_count` from `server_tool_use.web_search_requests`. Streamed generations used to under-report input tokens and cache writes and never recorded the web search count. diff --git a/.sampo/changesets/mcp-initialize-per-handshake.md b/.sampo/changesets/mcp-initialize-per-handshake.md deleted file mode 100644 index 33d926d0..00000000 --- a/.sampo/changesets/mcp-initialize-per-handshake.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -pypi/posthog: patch ---- - -MCP analytics now sends one `$mcp_initialize` for each `initialize` request that a client sends. - -Before, the SDK sent the event on the first request of each session, once per process. A stateless fleet sent one event for each pod that a session reached. A conversation-anchored session sent another event. A client that connected and sent no other request sent no event. - -On MCP SDK 2.x, the event comes from `Server.middleware` on every transport. Requests on the 2026-07-28 spec, which has no `initialize`, no longer send one. On MCP SDK 1.x over HTTP, the event comes from `PostHogMcpStatelessSessionMiddleware`. The apps that `instrument()` autowires get this with no change. If you mount the middleware by hand, pass the server as `server=`. MCP SDK 1.x over stdio keeps the first request as the trigger. - -The event now also has the handshake request as `$mcp_parameters`. On MCP SDK 2.x, it also has the server reply as `$mcp_response`. diff --git a/openfeature-provider/CHANGELOG.md b/openfeature-provider/CHANGELOG.md index 41701c15..db08dcd7 100644 --- a/openfeature-provider/CHANGELOG.md +++ b/openfeature-provider/CHANGELOG.md @@ -4,6 +4,12 @@ All notable changes to `openfeature-provider-posthog` are documented here. This file is maintained by [Sampo](https://github.com/bruits/sampo) from changesets in `.sampo/changesets/` that target `pypi/openfeature-provider-posthog`. +## 0.1.123 — 2026-10-23 + +### Patch changes + +- Updated dependencies: posthog@7.75.9 + ## 0.1.122 — 2026-10-23 ### Patch changes diff --git a/openfeature-provider/pyproject.toml b/openfeature-provider/pyproject.toml index f96e47bc..b1df3502 100644 --- a/openfeature-provider/pyproject.toml +++ b/openfeature-provider/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "openfeature-provider-posthog" -version = "0.1.122" +version = "0.1.123" description = "Official PostHog provider for the OpenFeature Python SDK." readme = "README.md" authors = [{ name = "PostHog", email = "engineering@posthog.com" }] diff --git a/posthog/CHANGELOG.md b/posthog/CHANGELOG.md index 5daf73be..5a6bf681 100644 --- a/posthog/CHANGELOG.md +++ b/posthog/CHANGELOG.md @@ -1,5 +1,18 @@ # posthog +## 7.75.9 — 2026-10-23 + +### Patch changes + +- [8cd8eb5](https://github.com/posthog/posthog-python/commit/8cd8eb5b6fdcdb19db4c4d07fe911c6eb3976d5a) MCP analytics now sends one `$mcp_initialize` for each `initialize` request that a client sends. + + Before, the SDK sent the event on the first request of each session, once per process. A stateless fleet sent one event for each pod that a session reached. A conversation-anchored session sent another event. A client that connected and sent no other request sent no event. + + On MCP SDK 2.x, the event comes from `Server.middleware` on every transport. Requests on the 2026-07-28 spec, which has no `initialize`, no longer send one. On MCP SDK 1.x over HTTP, the event comes from `PostHogMcpStatelessSessionMiddleware`. The apps that `instrument()` autowires get this with no change. If you mount the middleware by hand, pass the server as `server=`. MCP SDK 1.x over stdio keeps the first request as the trigger. + + The event now also has the handshake request as `$mcp_parameters`. On MCP SDK 2.x, it also has the server reply as `$mcp_response`. — Thanks @gesh! +- [48889b6](https://github.com/posthog/posthog-python/commit/48889b6cc8d0a9eec579684640cb74b393cdd56c) The Claude Agent SDK integration now takes the cumulative input and cache token counts from the final `message_delta` usage, which grow past the `message_start` values when server tools such as web search run, and sets `$ai_web_search_count` from `server_tool_use.web_search_requests`. Streamed generations used to under-report input tokens and cache writes and never recorded the web search count. — Thanks @pcbeingused333! + ## 7.75.8 — 2026-10-23 ### Patch changes diff --git a/posthog/version.py b/posthog/version.py index 42ba1010..50da243a 100644 --- a/posthog/version.py +++ b/posthog/version.py @@ -1 +1 @@ -VERSION = "7.75.8" +VERSION = "7.75.9" diff --git a/pyproject.toml b/pyproject.toml index b9442c52..cba52e44 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "posthog" -version = "7.75.8" +version = "7.75.9" description = "Integrate PostHog into any python application." authors = [{ name = "PostHog", email = "engineering@posthog.com" }] maintainers = [{ name = "PostHog", email = "engineering@posthog.com" }] diff --git a/uv.lock b/uv.lock index 8dbc9e20..52928cf7 100644 --- a/uv.lock +++ b/uv.lock @@ -2656,7 +2656,7 @@ wheels = [ [[package]] name = "openfeature-provider-posthog" -version = "0.1.122" +version = "0.1.123" source = { editable = "openfeature-provider" } dependencies = [ { name = "openfeature-sdk" }, @@ -3015,7 +3015,7 @@ wheels = [ [[package]] name = "posthog" -version = "7.75.8" +version = "7.75.9" source = { editable = "." } dependencies = [ { name = "distro" }, From 0bd78f4326c187f742f2326e6951d2808b5666f2 Mon Sep 17 00:00:00 2001 From: "releaser-posthog-python[bot]" <262414804+releaser-posthog-python[bot]@users.noreply.github.com> Date: Fri, 23 Oct 2026 12:38:57 +0000 Subject: [PATCH 12/60] Update generated references (cherry picked from commit e3fd9a49c6ca1beeb2d4f9b6a889ea7ee0774f57) --- .../posthog-python-references-7.75.9.json | 3548 +++++++++++++++++ .../posthog-python-references-latest.json | 2 +- 2 files changed, 3549 insertions(+), 1 deletion(-) create mode 100644 references/posthog-python-references-7.75.9.json diff --git a/references/posthog-python-references-7.75.9.json b/references/posthog-python-references-7.75.9.json new file mode 100644 index 00000000..3ced9019 --- /dev/null +++ b/references/posthog-python-references-7.75.9.json @@ -0,0 +1,3548 @@ +{ + "id": "posthog-python", + "hogRef": "0.3", + "info": { + "version": "7.75.9", + "id": "posthog-python", + "title": "PostHog Python SDK", + "description": "Integrate PostHog into any python application.", + "slugPrefix": "posthog-python", + "specUrl": "https://github.com/PostHog/posthog-python" + }, + "types": [ + { + "id": "FeatureFlag", + "name": "FeatureFlag", + "path": "posthog.types.FeatureFlag", + "properties": [ + { + "name": "key", + "type": "str", + "description": "Field: key" + }, + { + "name": "enabled", + "type": "bool", + "description": "Field: enabled" + }, + { + "name": "variant", + "type": "Optional[str]", + "description": "Field: variant" + }, + { + "name": "reason", + "type": "Optional[FlagReason]", + "description": "Field: reason" + }, + { + "name": "metadata", + "type": "Union[FlagMetadata, LegacyFlagMetadata]", + "description": "Field: metadata" + } + ], + "example": "" + }, + { + "id": "FeatureFlagResult", + "name": "FeatureFlagResult", + "path": "posthog.types.FeatureFlagResult", + "properties": [ + { + "name": "key", + "type": "str", + "description": "Field: key" + }, + { + "name": "enabled", + "type": "bool", + "description": "Field: enabled" + }, + { + "name": "variant", + "type": "Optional[str]", + "description": "Field: variant" + }, + { + "name": "payload", + "type": "Optional[Any]", + "description": "Field: payload" + }, + { + "name": "reason", + "type": "Optional[str]", + "description": "Field: reason" + } + ], + "example": "" + }, + { + "id": "FlagMetadata", + "name": "FlagMetadata", + "path": "posthog.types.FlagMetadata", + "properties": [ + { + "name": "id", + "type": "int", + "description": "Field: id" + }, + { + "name": "payload", + "type": "Optional[str]", + "description": "Field: payload" + }, + { + "name": "version", + "type": "int", + "description": "Field: version" + }, + { + "name": "description", + "type": "str", + "description": "Field: description" + }, + { + "name": "has_experiment", + "type": "Optional[bool]", + "description": "Field: has_experiment" + } + ], + "example": "" + }, + { + "id": "FlagReason", + "name": "FlagReason", + "path": "posthog.types.FlagReason", + "properties": [ + { + "name": "code", + "type": "str", + "description": "Field: code" + }, + { + "name": "condition_index", + "type": "Optional[int]", + "description": "Field: condition_index" + }, + { + "name": "description", + "type": "str", + "description": "Field: description" + } + ], + "example": "" + }, + { + "id": "FlagsAndPayloads", + "name": "FlagsAndPayloads", + "path": "posthog.types.FlagsAndPayloads", + "properties": [ + { + "name": "featureFlags", + "type": "Optional[dict[str, Union[bool, str]]]", + "description": "Field: featureFlags" + }, + { + "name": "featureFlagPayloads", + "type": "Optional[dict[str, Any]]", + "description": "Field: featureFlagPayloads" + } + ], + "example": "" + }, + { + "id": "FlagsResponse", + "name": "FlagsResponse", + "path": "posthog.types.FlagsResponse", + "properties": [ + { + "name": "flags", + "type": "dict[str, FeatureFlag]", + "description": "Field: flags" + }, + { + "name": "errorsWhileComputingFlags", + "type": "bool", + "description": "Field: errorsWhileComputingFlags" + }, + { + "name": "requestId", + "type": "str", + "description": "Field: requestId" + }, + { + "name": "quotaLimit", + "type": "Optional[list[str]]", + "description": "Field: quotaLimit" + }, + { + "name": "evaluatedAt", + "type": "Optional[int]", + "description": "Field: evaluatedAt" + }, + { + "name": "minimalFlagCalledEvents", + "type": "bool", + "description": "Field: minimalFlagCalledEvents" + } + ], + "example": "" + }, + { + "id": "LegacyFlagMetadata", + "name": "LegacyFlagMetadata", + "path": "posthog.types.LegacyFlagMetadata", + "properties": [ + { + "name": "payload", + "type": "Any", + "description": "Field: payload" + } + ], + "example": "" + }, + { + "id": "SendFeatureFlagsOptions", + "name": "SendFeatureFlagsOptions", + "path": "posthog.types.SendFeatureFlagsOptions", + "properties": [ + { + "name": "should_send", + "type": "bool", + "description": "Field: should_send" + }, + { + "name": "only_evaluate_locally", + "type": "Optional[bool]", + "description": "Field: only_evaluate_locally" + }, + { + "name": "person_properties", + "type": "Optional[dict[str, Any]]", + "description": "Field: person_properties" + }, + { + "name": "group_properties", + "type": "Optional[dict[str, dict[str, Any]]]", + "description": "Field: group_properties" + }, + { + "name": "flag_keys_filter", + "type": "Optional[list[str]]", + "description": "Field: flag_keys_filter" + } + ], + "example": "" + }, + { + "id": "OptionalCaptureArgs", + "name": "OptionalCaptureArgs", + "path": "posthog.args.OptionalCaptureArgs", + "properties": [ + { + "name": "distinct_id", + "type": "NotRequired[Union[Number, str, UUID, int, any]]", + "description": "Field: distinct_id" + }, + { + "name": "properties", + "type": "NotRequired[Optional[dict[str, Any]]]", + "description": "Field: properties" + }, + { + "name": "timestamp", + "type": "NotRequired[Union[datetime, str, any]]", + "description": "Field: timestamp" + }, + { + "name": "uuid", + "type": "NotRequired[Union[str, UUID, any]]", + "description": "Field: uuid" + }, + { + "name": "groups", + "type": "NotRequired[Optional[dict[str, str]]]", + "description": "Field: groups" + }, + { + "name": "flags", + "type": "NotRequired[Optional[ForwardRef('FeatureFlagEvaluations')]]", + "description": "Field: flags" + }, + { + "name": "send_feature_flags", + "type": "NotRequired[Union[bool, SendFeatureFlagsOptions, any]]", + "description": "Field: send_feature_flags" + }, + { + "name": "disable_geoip", + "type": "NotRequired[Optional[bool]]", + "description": "Field: disable_geoip" + }, + { + "name": "_property_allowlist", + "type": "NotRequired[Optional[frozenset[str]]]", + "description": "Field: _property_allowlist" + } + ], + "example": "" + }, + { + "id": "OptionalSetArgs", + "name": "OptionalSetArgs", + "path": "posthog.args.OptionalSetArgs", + "properties": [ + { + "name": "distinct_id", + "type": "NotRequired[Union[Number, str, UUID, int, any]]", + "description": "Field: distinct_id" + }, + { + "name": "properties", + "type": "NotRequired[Optional[dict[str, Any]]]", + "description": "Field: properties" + }, + { + "name": "timestamp", + "type": "NotRequired[Union[datetime, str, any]]", + "description": "Field: timestamp" + }, + { + "name": "uuid", + "type": "NotRequired[Union[str, UUID, any]]", + "description": "Field: uuid" + }, + { + "name": "disable_geoip", + "type": "NotRequired[Optional[bool]]", + "description": "Field: disable_geoip" + } + ], + "example": "" + }, + { + "id": "SendFeatureFlagsOptions", + "name": "SendFeatureFlagsOptions", + "path": "posthog.types.SendFeatureFlagsOptions", + "properties": [ + { + "name": "should_send", + "type": "bool", + "description": "Field: should_send" + }, + { + "name": "only_evaluate_locally", + "type": "Optional[bool]", + "description": "Field: only_evaluate_locally" + }, + { + "name": "person_properties", + "type": "Optional[dict[str, Any]]", + "description": "Field: person_properties" + }, + { + "name": "group_properties", + "type": "Optional[dict[str, dict[str, Any]]]", + "description": "Field: group_properties" + }, + { + "name": "flag_keys_filter", + "type": "Optional[list[str]]", + "description": "Field: flag_keys_filter" + } + ], + "example": "" + } + ], + "classes": [ + { + "id": "PostHog", + "title": "PostHog", + "description": "This is the SDK reference for the PostHog Python SDK. You can learn more about example usage in the [Python SDK documentation](/docs/libraries/python). You can also follow [Flask](/docs/libraries/flask) and [Django](/docs/libraries/django) guides to integrate PostHog into your project. For long-running applications, create one client during application startup and reuse it for the lifetime of the process. This keeps background queues predictable and makes shutdown flushing straightforward. Multiple clients are still supported for intentional multi-project or multi-host setups.", + "functions": [ + { + "id": "__init__", + "title": "Client", + "description": "Initialize a new PostHog client instance.", + "details": "", + "category": "Initialization", + "params": [ + { + "name": "project_api_key", + "description": "PostHog project API key/token.", + "isOptional": true, + "type": "str" + }, + { + "name": "host", + "description": "PostHog host. Defaults to the US ingestion endpoint when not set. App hosts such as ``https://us.posthog.com`` are mapped to the corresponding ingestion host.", + "isOptional": false, + "type": "any" + }, + { + "name": "debug", + "description": "Enable verbose SDK logging and re-raise errors from public API methods.", + "isOptional": false, + "type": "bool" + }, + { + "name": "max_queue_size", + "description": "Maximum number of events buffered before upload.", + "isOptional": false, + "type": "int" + }, + { + "name": "send", + "description": "If False, queueing succeeds but events are not sent.", + "isOptional": false, + "type": "bool" + }, + { + "name": "on_error", + "description": "Optional callback invoked by background consumers when an upload fails. Keep it short and non-blocking. Calling lifecycle methods directly is safe and deferred, but do not start another thread or task that calls ``flush()``, ``join()``, or ``shutdown()`` and then wait for it from the callback.", + "isOptional": false, + "type": "any" + }, + { + "name": "flush_at", + "description": "Number of queued events that triggers a batch upload.", + "isOptional": false, + "type": "int" + }, + { + "name": "flush_interval", + "description": "Maximum seconds a background consumer waits before flushing a partial batch.", + "isOptional": false, + "type": "float" + }, + { + "name": "gzip", + "description": "Whether to gzip event upload payloads.", + "isOptional": false, + "type": "bool" + }, + { + "name": "max_retries", + "description": "Number of upload retries. Values below 0 are treated as 0.", + "isOptional": false, + "type": "int" + }, + { + "name": "sync_mode", + "description": "If True, send each event synchronously instead of using background worker threads. This blocks the calling thread; in asyncio applications such as FastAPI, use ``AsyncPosthog`` instead.", + "isOptional": false, + "type": "bool" + }, + { + "name": "timeout", + "description": "HTTP request timeout in seconds for event uploads.", + "isOptional": false, + "type": "float" + }, + { + "name": "thread", + "description": "Number of background consumer threads.", + "isOptional": false, + "type": "int" + }, + { + "name": "poll_interval", + "description": "Seconds between local feature flag definition refreshes.", + "isOptional": false, + "type": "int" + }, + { + "name": "personal_api_key", + "description": "Deprecated alias for ``secret_key``. Still honored for backwards compatibility; prefer ``secret_key``, which also accepts a Project Secret API Key.", + "isOptional": false, + "type": "any" + }, + { + "name": "disabled", + "description": "If True, disable captures and API requests. Useful in tests.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable server-side GeoIP enrichment. Defaults to True.", + "isOptional": false, + "type": "bool" + }, + { + "name": "is_server", + "description": "Whether events are emitted from a server-side runtime. Defaults to True; set to False when using the SDK as a client/CLI so the device OS is attributed to the person normally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "historical_migration", + "description": "Mark events as historical migration imports.", + "isOptional": false, + "type": "bool" + }, + { + "name": "feature_flags_request_timeout_seconds", + "description": "Timeout in seconds for feature flag and remote config requests.", + "isOptional": false, + "type": "float" + }, + { + "name": "feature_flags_request_max_retries", + "description": "Number of retries for feature flag requests after network, transport, or timeout failures. Defaults to 1. Set to 0 to disable retries.", + "isOptional": false, + "type": "int" + }, + { + "name": "super_properties", + "description": "Properties merged into every captured event.", + "isOptional": false, + "type": "any" + }, + { + "name": "enable_exception_autocapture", + "description": "Automatically capture uncaught exceptions.", + "isOptional": false, + "type": "bool" + }, + { + "name": "log_captured_exceptions", + "description": "Also log exceptions captured by error tracking.", + "isOptional": false, + "type": "bool" + }, + { + "name": "project_root", + "description": "Root path used to determine in-app stack frames for captured exceptions. Defaults to the current working directory.", + "isOptional": false, + "type": "any" + }, + { + "name": "privacy_mode", + "description": "For AI observability, capture usage metadata without prompt inputs or outputs.", + "isOptional": false, + "type": "bool" + }, + { + "name": "before_send", + "description": "Optional callback that can modify or drop events before upload. Return ``None`` to drop an event.", + "isOptional": false, + "type": "any" + }, + { + "name": "flag_fallback_cache_url", + "description": "Optional feature flag fallback cache URL, such as ``memory://local/?ttl=300&size=10000`` or a Redis URL.", + "isOptional": false, + "type": "any" + }, + { + "name": "enable_local_evaluation", + "description": "Whether to poll feature flag definitions for local evaluation when a personal API key is configured.", + "isOptional": false, + "type": "bool" + }, + { + "name": "flag_definition_cache_provider", + "description": "Optional external cache provider for sharing feature flag definitions across workers.", + "isOptional": true, + "type": "FlagDefinitionCacheProvider" + }, + { + "name": "capture_exception_code_variables", + "description": "Capture local variable values on exception stack frames.", + "isOptional": false, + "type": "bool" + }, + { + "name": "code_variables_mask_patterns", + "description": "Variable-name patterns to mask when capturing code variables.", + "isOptional": false, + "type": "any" + }, + { + "name": "code_variables_ignore_patterns", + "description": "Variable-name patterns to omit when capturing code variables.", + "isOptional": false, + "type": "any" + }, + { + "name": "code_variables_mask_url_credentials", + "description": "Scrub credentials embedded in URLs/DSNs (e.g. ``user:pass@host``) from captured code variables, regardless of the surrounding variable name. Defaults to True.", + "isOptional": false, + "type": "any" + }, + { + "name": "code_variables_detect_secrets", + "description": "Last-resort entropy-based detection that redacts high-entropy secret-looking values (API keys, tokens, strong passwords) sitting in innocuously-named variables, after the name and URL checks. Skips structured ids (UUIDs, ObjectIds, hashes). Defaults to True.", + "isOptional": false, + "type": "any" + }, + { + "name": "in_app_modules", + "description": "Module/package prefixes treated as in-app frames in captured exceptions.", + "isOptional": false, + "type": "UnionType[list[str], any]" + }, + { + "name": "enable_exception_autocapture_rate_limiting", + "description": "Rate limit autocaptured exceptions client-side with a token bucket per exception type. Disabled by default.", + "isOptional": false, + "type": "bool" + }, + { + "name": "exception_autocapture_bucket_size", + "description": "Maximum burst of autocaptured exceptions allowed per exception type (token bucket size, clamped to 0-100).", + "isOptional": false, + "type": "int" + }, + { + "name": "exception_autocapture_refill_rate", + "description": "Tokens restored per refill interval for each exception type's bucket.", + "isOptional": false, + "type": "int" + }, + { + "name": "exception_autocapture_refill_interval_seconds", + "description": "Seconds between token refills for autocaptured exception rate limiting.", + "isOptional": false, + "type": "int" + }, + { + "name": "capture_mode", + "description": "Capture wire protocol to use. Defaults to ``CaptureMode.V0`` (legacy ``/batch/``). Set ``CaptureMode.V1`` (or pass the string ``\"v1\"``) to opt into ``/i/v1/analytics/events``. When omitted, the ``POSTHOG_CAPTURE_MODE`` env var is consulted, then ``V0``.", + "isOptional": false, + "type": "CaptureMode" + }, + { + "name": "capture_compression", + "description": "Request-body compression for capture-v1 uploads (ignored in V0, which uses ``gzip``). ``CaptureCompression.GZIP`` or ``DEFLATE`` (or the strings ``\"gzip\"``/``\"deflate\"``). When omitted, the ``POSTHOG_CAPTURE_COMPRESSION`` env var is consulted, then the legacy ``gzip`` flag, then no compression.", + "isOptional": false, + "type": "CaptureCompression" + }, + { + "name": "secret_key", + "description": "A Personal API Key or Project Secret API Key, used to authenticate local feature flag evaluation, remote config payloads, and decrypted flag payloads. Example:: posthog.Client(project_api_key, secret_key=\"phx_...\")", + "isOptional": false, + "type": "any" + }, + { + "name": "metrics", + "description": "", + "isOptional": true, + "type": "dict" + }, + { + "name": "enable_full_ai_capture", + "description": "Route PostHog AI wrapper events through the dedicated AI capture endpoint and capture full AI content: skips string truncation and passes media (base64/data URIs) through unredacted. ``privacy_mode`` always wins. Defaults to False.", + "isOptional": false, + "type": "bool" + }, + { + "name": "capture_trace_context", + "description": "When OpenTelemetry is installed and a valid span is active at capture time, add its trace and span IDs as ``$trace_id`` and ``$span_id`` properties to events captured with ``capture()`` and ``capture_ai()``, so they can be correlated with backend traces. Explicit ``$trace_id``/``$span_id`` values passed in ``properties`` win. Exception events (``capture_exception``) always attach these IDs regardless of this setting. Defaults to False.", + "isOptional": false, + "type": "bool" + }, + { + "name": "_use_ai_lane", + "description": "", + "isOptional": false, + "type": "bool" + }, + { + "name": "_enable_multimodal_capture", + "description": "", + "isOptional": false, + "type": "bool" + }, + { + "name": "traces", + "description": "Config dict for distributed tracing: ``service_name``, ``service_version``, ``environment``, ``resource_attributes``, ``flush_interval`` (5 s), ``max_queue_size`` (2048), ``max_export_batch_size`` (512), ``max_live_spans`` (10000), ``max_span_age`` (3600 s), ``max_attributes_per_span`` (128), ``max_events_per_span`` (128), ``max_attribute_value_length`` (8192). ``before_span_send`` is a callable, or a list run in order, that receives each finished span as a dict (``trace_id``, ``span_id`` and ``parent_span_id`` are read-only) and returns it, edited, or ``None`` to drop it; a hook that raises drops the span. Tracing is off until this is provided. Spans export on a background timer even with ``sync_mode``; serverless handlers should call ``flush()`` before returning. Defaults to None.", + "isOptional": true, + "type": "dict" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import Posthog\n\nposthog = Posthog('', host='')" + } + ] + }, + { + "id": "alias", + "title": "alias", + "description": "Create an alias between two distinct IDs.", + "details": "", + "category": "Identification", + "params": [ + { + "name": "previous_id", + "description": "The previous distinct ID. Required - the call is dropped with a warning if it is missing or empty.", + "isOptional": true, + "type": "Number" + }, + { + "name": "distinct_id", + "description": "The new distinct ID to alias to. Falls back to the context distinct ID; the call is dropped with a warning if neither is available.", + "isOptional": true, + "type": "str" + }, + { + "name": "timestamp", + "description": "The timestamp of the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC.", + "isOptional": false, + "type": "datetime" + }, + { + "name": "uuid", + "description": "A unique identifier for the event. If provided, it must be a valid UUID string or uuid.UUID instance; invalid values are ignored and replaced with a newly generated UUID.", + "isOptional": true, + "type": "str" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this event.", + "isOptional": true, + "type": "bool" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.alias(previous_id='distinct_id', distinct_id='alias_id')" + } + ] + }, + { + "id": "capture", + "title": "capture", + "description": "Captures an event manually. [Learn about capture best practices](https://posthog.com/docs/product-analytics/capture-events)", + "details": "", + "category": "Capture", + "params": [ + { + "name": "event", + "description": "The event name to capture.", + "isOptional": true, + "type": "str" + }, + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalCaptureArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Anonymous event", + "code": "# Anonymous event\nposthog.capture('some-anon-event')" + }, + { + "id": "example_2", + "name": "Context usage", + "code": "# Context usage\nfrom posthog import identify_context, new_context\nwith new_context():\n identify_context('distinct_id_of_the_user')\n posthog.capture('user_signed_up')\n posthog.capture('user_logged_in')\n posthog.capture('some-custom-action', distinct_id='distinct_id_of_the_user')" + }, + { + "id": "example_3", + "name": "Set event properties", + "code": "# Set event properties\nposthog.capture(\n \"user_signed_up\",\n distinct_id=\"distinct_id_of_the_user\",\n properties={\n \"login_type\": \"email\",\n \"is_free_trial\": \"true\"\n }\n)" + }, + { + "id": "example_4", + "name": "Page view event", + "code": "# Page view event\nposthog.capture('$pageview', distinct_id=\"distinct_id_of_the_user\", properties={'$current_url': 'https://example.com'})" + } + ] + }, + { + "id": "capture_ai", + "title": "capture_ai", + "description": "Capture an AI event on the dedicated AI capture endpoint. Beta: the signature is stable; operational limits (per-event size cap, batching, endpoint) may change without notice. Takes the same arguments and returns the same value as `capture()`: the event UUID, or None when the event was not admitted (disabled client, or dropped by `before_send`). The event is queued on an isolated AI lane with its own consumer pool and a higher per-event size cap, posting to the dedicated AI ingestion endpoint. The payload is sent as given \u2014 no redaction or truncation is applied here.", + "details": "", + "category": "Capture", + "params": [ + { + "name": "event", + "description": "", + "isOptional": true, + "type": "str" + }, + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalCaptureArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + } + }, + { + "id": "capture_exception", + "title": "capture_exception", + "description": "Capture an exception for error tracking. When OpenTelemetry is installed and a valid span is active, its trace and span IDs are added as ``$trace_id`` and ``$span_id`` event properties.", + "details": "", + "category": "Error Tracking", + "params": [ + { + "name": "exception", + "description": "The exception to capture.", + "isOptional": true, + "type": "BaseException" + }, + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalCaptureArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "try:\n # Some code that might fail\n pass\nexcept Exception as e:\n posthog.capture_exception(e, 'user_distinct_id', properties=additional_properties)" + } + ] + }, + { + "id": "evaluate_flags", + "title": "evaluate_flags", + "description": "Evaluate all feature flags for a user in a single call and return a :class:`FeatureFlagEvaluations` snapshot. Branch on ``.is_enabled()`` / ``.get_flag()`` and pass the same snapshot to :meth:`capture` via the ``flags`` option so events carry the exact flag values the code branched on. Prefer this over repeated ``get_feature_flag()`` calls and over ``capture(send_feature_flags=True)`` \u2014 it consolidates flag evaluation into a single ``/flags`` request per incoming request. Local evaluation is transparent: when the poller resolves a flag, the snapshot's ``$feature_flag_called`` events are tagged ``locally_evaluated=True`` and reason ``\"Evaluated locally\"``.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The user's distinct ID. If ``None``, falls back to the context distinct_id. If still unresolvable, returns an empty snapshot.", + "isOptional": false, + "type": "Number" + }, + { + "name": "groups", + "description": "Mapping of group type to group key.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "Person properties to use for evaluation.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "Group properties keyed by group type.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "If True, never fall back to remote evaluation \u2014 flags that can't be evaluated locally are simply omitted from the snapshot.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup.", + "isOptional": true, + "type": "bool" + }, + { + "name": "flag_keys", + "description": "Optional list that scopes local evaluation, the underlying ``/flags`` request, and the returned snapshot. When omitted or ``None``, all flags are evaluated. An empty list returns an empty snapshot without evaluating flags. A requested key absent from loaded local definitions is included in one remote fallback per ``evaluate_flags`` call unless ``only_evaluate_locally`` is True. If the server also does not know the key, it is omitted from the snapshot.", + "isOptional": true, + "type": "list[str]" + }, + { + "name": "device_id", + "description": "Optional device ID override. If not provided, falls back to the context device_id (which may be set via tracing headers). Used by experience-continuity flags to match users across distinct_id changes.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "FeatureFlagEvaluations" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "flags = posthog.evaluate_flags(\n \"user_123\",\n person_properties={\"plan\": \"enterprise\"},\n)\nif flags.is_enabled(\"new-dashboard\"):\n render_new_dashboard()\nposthog.capture(\"page_viewed\", distinct_id=\"user_123\", flags=flags)" + } + ] + }, + { + "id": "feature_enabled", + "title": "feature_enabled", + "description": "Check if a feature flag is enabled for a user.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "The feature flag key.", + "isOptional": true, + "type": "str" + }, + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to only evaluate locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "send_feature_flag_events", + "description": "Whether to send feature flag events.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[bool]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "is_my_flag_enabled = posthog.feature_enabled('flag-key', 'distinct_id_of_your_user')\nif is_my_flag_enabled:\n # Do something differently for this user\n # Optional: fetch the payload\n matched_flag_payload = posthog.get_feature_flag_payload('flag-key', 'distinct_id_of_your_user')" + } + ] + }, + { + "id": "feature_flag_definitions", + "title": "feature_flag_definitions", + "description": "Return feature flag definitions loaded for local evaluation. Returns: The currently loaded feature flag definitions, or ``None`` before local evaluation has loaded definitions.", + "details": "", + "category": "Feature flags", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "flush", + "title": "flush", + "description": "Attempt immediate delivery of queued events and ended spans, keeping the client reusable. For a client shared across requests or serverless invocations (including the module-level client), call ``flush()`` before a short-lived invocation returns. Use ``shutdown()`` instead only for final process cleanup or when disposing of a client that will not be reused. Flush does not stop the background workers or feature flag poller, and does not end open spans. Returning does not guarantee server receipt: pending work can remain after a timeout or span export failure, and failed events may be dropped by the consumers. Calls made directly from SDK callbacks such as ``on_error`` are deferred rather than blocking the worker that invoked the callback.", + "details": "", + "category": null, + "params": [ + { + "name": "timeout_seconds", + "description": "Seconds to wait for event queues to drain, shared across event lanes. Defaults to 10 seconds. Pass ``None`` to wait without a time limit; this does not guarantee successful delivery. Queued spans flush concurrently. The span exporter waits up to this many seconds for an in-flight flush, then uses a fresh drain budget once it acquires the lock. If the lock wait times out, the in-flight flush is left to finish. At least one span request is attempted after acquiring the lock, even with no budget left; retriable failures are retried after backoff while budget remains, with one last attempt at the deadline. Each span request is bounded by the client's ``timeout`` in seconds, and the last request is not cut short. This is therefore not a strict wall-clock limit for the whole call. With ``None``, a retriable span failure is left for a later flush rather than retried within this call.", + "isOptional": true, + "type": "float" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.capture('event_name', distinct_id='user_id')\n# Before a serverless invocation returns; keep the shared client open.\nposthog.flush(timeout_seconds=10)" + } + ] + }, + { + "id": "get_active_span", + "title": "get_active_span", + "description": "The span that is active in the current context, or ``None``. Alpha. Only entering a span (``with posthog.start_span(...) as span:``) makes it active; a span started manually is not. Use it to propagate the trace to the next service: ``span.traceparent()`` is the header value.", + "details": "", + "category": "Tracing", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[Span]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "span = posthog.get_active_span()\nif span is not None:\n headers[\"traceparent\"] = span.traceparent()" + } + ] + }, + { + "id": "get_all_flags", + "title": "get_all_flags", + "description": "Get all feature flags for a user.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to only evaluate locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "flag_keys_to_evaluate", + "description": "A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance.", + "isOptional": true, + "type": "list[str]" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[dict[str, Union[bool, str]]]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.get_all_flags('distinct_id_of_your_user')" + } + ] + }, + { + "id": "get_all_flags_and_payloads", + "title": "get_all_flags_and_payloads", + "description": "Get all feature flags and their payloads for a user.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to only evaluate locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "flag_keys_to_evaluate", + "description": "A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance.", + "isOptional": true, + "type": "list[str]" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "FlagsAndPayloads" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.get_all_flags_and_payloads('distinct_id_of_your_user')" + } + ] + }, + { + "id": "get_feature_flag", + "title": "get_feature_flag", + "description": "Get multivariate feature flag value for a user.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "The feature flag key.", + "isOptional": true, + "type": "str" + }, + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to only evaluate locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "send_feature_flag_events", + "description": "Whether to send feature flag events.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Union[bool, str, any]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "enabled_variant = posthog.get_feature_flag('flag-key', 'distinct_id_of_your_user')\nif enabled_variant == 'variant-key': # replace 'variant-key' with the key of your variant\n # Do something differently for this user\n # Optional: fetch the payload\n matched_flag_payload = posthog.get_feature_flag_payload('flag-key', 'distinct_id_of_your_user')" + } + ] + }, + { + "id": "get_feature_flag_evaluation_runtime", + "title": "get_feature_flag_evaluation_runtime", + "description": "Return where a locally loaded feature flag is meant to be evaluated.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "The feature flag key.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[FeatureFlagEvaluationRuntime]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import FeatureFlagEvaluationRuntime\n\nruntime = posthog.get_feature_flag_evaluation_runtime(\"my-flag\")\nif runtime is FeatureFlagEvaluationRuntime.SERVER:\n ..." + } + ] + }, + { + "id": "get_feature_flag_keys_by_evaluation_runtime", + "title": "get_feature_flag_keys_by_evaluation_runtime", + "description": "Return the keys of locally loaded flags that a runtime can evaluate. A flag set to ``FeatureFlagEvaluationRuntime.ALL`` suits either runtime, so it is returned for ``CLIENT`` and for ``SERVER``, and asking for ``ALL`` returns every loaded flag. Use this to decide which flags to hand to a browser when a backend serves flags to its own frontend.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "evaluation_runtime", + "description": "The runtime to match, as a ``FeatureFlagEvaluationRuntime`` or its string value.", + "isOptional": true, + "type": "FeatureFlagEvaluationRuntime" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "list[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import FeatureFlagEvaluationRuntime\n\nclient_keys = posthog.get_feature_flag_keys_by_evaluation_runtime(\n FeatureFlagEvaluationRuntime.CLIENT\n)" + } + ] + }, + { + "id": "get_feature_flag_payload", + "title": "get_feature_flag_payload", + "description": "Get the payload for a feature flag.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "The feature flag key.", + "isOptional": true, + "type": "str" + }, + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "match_value", + "description": "The specific flag value to get payload for.", + "isOptional": false, + "type": "bool" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to only evaluate locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "send_feature_flag_events", + "description": "Deprecated. Use get_feature_flag() instead if you need events.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[object]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "is_my_flag_enabled = posthog.feature_enabled('flag-key', 'distinct_id_of_your_user')\n\nif is_my_flag_enabled:\n # Do something differently for this user\n # Optional: fetch the payload\n matched_flag_payload = posthog.get_feature_flag_payload('flag-key', 'distinct_id_of_your_user')" + } + ] + }, + { + "id": "get_feature_flag_result", + "title": "get_feature_flag_result", + "description": "Get a FeatureFlagResult object which contains the flag result and payload for a key by evaluating locally or remotely depending on whether local evaluation is enabled and the flag can be locally evaluated. This also captures the `$feature_flag_called` event unless `send_feature_flag_events` is `False`.", + "details": "", + "category": null, + "params": [ + { + "name": "key", + "description": "The feature flag key.", + "isOptional": true, + "type": "str" + }, + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to only evaluate locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "send_feature_flag_events", + "description": "Whether to send feature flag events.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[FeatureFlagResult]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "flag_result = posthog.get_feature_flag_result('flag-key', 'distinct_id_of_your_user')\nif flag_result and flag_result.get_value() == 'variant-key':\n # Do something differently for this user\n # Optional: fetch the payload\n matched_flag_payload = flag_result.payload" + } + ] + }, + { + "id": "get_feature_flags_and_payloads", + "title": "get_feature_flags_and_payloads", + "description": "Get feature flags and payloads for a user.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "flag_keys_to_evaluate", + "description": "A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance.", + "isOptional": true, + "type": "list[str]" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "FlagsAndPayloads" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "result = posthog.get_feature_flags_and_payloads('')" + } + ] + }, + { + "id": "get_feature_payloads", + "title": "get_feature_payloads", + "description": "Get feature flag payloads for a user, preserving valid serialized JSON.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "flag_keys_to_evaluate", + "description": "A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance.", + "isOptional": true, + "type": "list[str]" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "dict[str, Optional[str]]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "payloads = posthog.get_feature_payloads('')" + } + ] + }, + { + "id": "get_feature_variants", + "title": "get_feature_variants", + "description": "Get feature flag variants for a user.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "flag_keys_to_evaluate", + "description": "A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance.", + "isOptional": true, + "type": "list[str]" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "dict[str, Union[bool, str]]" + } + }, + { + "id": "get_flags_decision", + "title": "get_flags_decision", + "description": "Get feature flags decision.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": false, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "flag_keys_to_evaluate", + "description": "A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance.", + "isOptional": true, + "type": "list[str]" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "FlagsResponse" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "decision = posthog.get_flags_decision('user123')" + } + ] + }, + { + "id": "get_remote_config_payload", + "title": "get_remote_config_payload", + "description": "Get the payload for a remote config feature flag.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "The remote config feature flag key.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "get_tags", + "title": "get_tags", + "description": "Get all tags from the current context. Returns: Dict of all tags in the current context.", + "details": "", + "category": "Contexts", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "dict[str, Any]" + } + }, + { + "id": "group_identify", + "title": "group_identify", + "description": "Identify a group and set its properties.", + "details": "", + "category": "Identification", + "params": [ + { + "name": "group_type", + "description": "The type of group (e.g., 'company', 'team'). Required - the call is dropped with a warning if it is missing or empty.", + "isOptional": true, + "type": "str" + }, + { + "name": "group_key", + "description": "The unique identifier for the group. Required - the call is dropped with a warning if it is missing or empty.", + "isOptional": true, + "type": "str" + }, + { + "name": "properties", + "description": "A dictionary of properties to set on the group.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "timestamp", + "description": "The timestamp of the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC.", + "isOptional": false, + "type": "datetime" + }, + { + "name": "uuid", + "description": "A unique identifier for the event. If provided, it must be a valid UUID string or uuid.UUID instance; invalid values are ignored and replaced with a newly generated UUID.", + "isOptional": false, + "type": "str" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this event.", + "isOptional": true, + "type": "bool" + }, + { + "name": "distinct_id", + "description": "The distinct ID of the user performing the action.", + "isOptional": false, + "type": "Number" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.group_identify('company', 'company_id_in_your_db', {\n 'name': 'Awesome Inc.',\n 'employees': 11\n})" + } + ] + }, + { + "id": "identify_context", + "title": "identify_context", + "description": "Identify the current context with a distinct ID.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "distinct_id", + "description": "The distinct ID to associate with the current context and its children.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + } + }, + { + "id": "join", + "title": "join", + "description": "Attempt to process queued events and end the consumer threads. Do not use directly, call `shutdown()` instead. Failed or undrainable events may be dropped and reported through logging or ``on_error``; returning does not guarantee server receipt. Lifecycle cleanup is attempted once, and cleanup failures are logged without retry.", + "details": "", + "category": null, + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.join()" + } + ] + }, + { + "id": "load_feature_flags", + "title": "load_feature_flags", + "description": "Load feature flags for local evaluation.", + "details": "", + "category": "Feature flags", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.load_feature_flags()" + } + ] + }, + { + "id": "new_context", + "title": "new_context", + "description": "Create a new context for managing shared state. Learn more about [contexts](/docs/libraries/python#contexts).", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "fresh", + "description": "Whether to create a fresh context that doesn't inherit from parent.", + "isOptional": false, + "type": "bool" + }, + { + "name": "capture_exceptions", + "description": "Whether to automatically capture exceptions in this context. If omitted, defaults to this client's exception autocapture setting.", + "isOptional": true, + "type": "bool" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "with client.new_context():\n client.identify_context('')\n client.capture('event_name')" + } + ] + }, + { + "id": "scoped", + "title": "scoped", + "description": "Decorator that creates a new context for the wrapped function using this client.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "fresh", + "description": "Whether to create a fresh context that doesn't inherit from parent.", + "isOptional": false, + "type": "bool" + }, + { + "name": "capture_exceptions", + "description": "Whether to automatically capture exceptions in this context. If omitted, defaults to this client's exception autocapture setting.", + "isOptional": true, + "type": "bool" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "set", + "title": "set", + "description": "Set properties on a person profile.", + "details": "", + "category": "Identification", + "params": [ + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalSetArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Set with distinct id", + "code": "# Set with distinct id\nposthog.set(distinct_id='user123', properties={'name': 'Max Hedgehog'})" + } + ] + }, + { + "id": "set_context_device_id", + "title": "set_context_device_id", + "description": "Set the device ID for the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "device_id", + "description": "The device ID to associate with the current context and its children.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + } + }, + { + "id": "set_context_session", + "title": "set_context_session", + "description": "Set the session ID for the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "session_id", + "description": "The session ID to associate with the current context and its children.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + } + }, + { + "id": "set_once", + "title": "set_once", + "description": "Set properties on a person profile only if they haven't been set before.", + "details": "", + "category": "Identification", + "params": [ + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalSetArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.set_once(distinct_id='user123', properties={'initial_signup_date': '2024-01-01'})" + } + ] + }, + { + "id": "shutdown", + "title": "shutdown", + "description": "Attempt final delivery and permanently shut down the client. Call this for final process cleanup or when disposing of a client that will not be reused. It closes capture lanes, stops background workers and the feature flag poller, and tears down integrations. Do not call it at the end of each request or serverless invocation when the client is shared; call ``flush()`` before returning instead. A shut-down client cannot be reused; create a new client if needed. Normally this method blocks until queued events have been attempted and cleanup finishes. Failed or undrainable events may be dropped and reported through logging or ``on_error``; returning does not guarantee server receipt. Event draining has no timeout. Queued spans get one final flush with up to 30 seconds waiting for an in-flight flush, then a fresh 30-second drain budget; the last HTTP request is not cut short. Any spans it cannot send are discarded with a warning, as are spans still open. Lifecycle cleanup is attempted once, and cleanup failures are logged without retry. When called directly from an SDK callback such as ``on_error``, shutdown is deferred to avoid blocking the worker that invoked the callback. If the callback must coordinate a blocking shutdown, have it signal an application-owned thread and return before that thread calls shutdown. Do not wait inside the callback for another thread or task that calls a lifecycle method.", + "details": "", + "category": null, + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.shutdown()" + } + ] + }, + { + "id": "start_span", + "title": "start_span", + "description": "Start a span for distributed tracing. Alpha. Returns a span handle. Use it as a context manager to make it the active span for the block and end it on exit (recording a raised exception on the way out); or call ``end()`` yourself for a span that cannot wrap a block. Spans started inside the block nest under it automatically. Always returns a usable handle, even when tracing is off, so calling code never branches.", + "details": "", + "category": "Tracing", + "params": [ + { + "name": "name", + "description": "A low-cardinality operation name, e.g. ``GET /users/:id``. Variable values belong in attributes, not the name.", + "isOptional": true, + "type": "str" + }, + { + "name": "kind", + "description": "``internal`` (default), ``server``, ``client``, ``producer`` or ``consumer``.", + "isOptional": true, + "type": "str" + }, + { + "name": "attributes", + "description": "Initial attributes.", + "isOptional": true, + "type": "Mapping[str, Any]" + }, + { + "name": "parent", + "description": "A span handle, or an inbound W3C ``traceparent`` header value to continue a remote trace. Defaults to the active span. A forked child starts with no active span; pass the parent span to continue a trace across a fork.", + "isOptional": false, + "type": "Span" + }, + { + "name": "tracestate", + "description": "The inbound ``tracestate`` header accompanying a ``traceparent`` string ``parent``; preserved and propagated.", + "isOptional": true, + "type": "str" + }, + { + "name": "start_time", + "description": "A ``datetime`` or epoch seconds, to backdate the span.", + "isOptional": false, + "type": "datetime" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Span" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog = Posthog(\"\", traces={\"service_name\": \"checkout-api\"})\n\nwith posthog.start_span(\"POST /checkout\", parent=request.headers.get(\"traceparent\")) as span:\n span.set_attribute(\"plan\", user.plan)\n with posthog.start_span(\"db.query\", kind=\"client\"):\n ...\n outgoing_headers = {\"traceparent\": span.traceparent()}" + } + ] + }, + { + "id": "tag", + "title": "tag", + "description": "Add a tag to the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "name", + "description": "The tag key.", + "isOptional": true, + "type": "str" + }, + { + "name": "value", + "description": "The tag value.", + "isOptional": true, + "type": "Any" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + } + } + ] + }, + { + "id": "PostHogModule", + "title": "PostHog Module Functions", + "description": "Global functions available in the PostHog module", + "functions": [ + { + "id": "alias", + "title": "alias", + "description": "Associate user behaviour before and after they e.g. register, login, or perform some other identifying action.", + "details": "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?\". Particularly useful for associating user behaviour before and after they e.g. register, login, or perform some other identifying action.", + "category": "Identification", + "params": [ + { + "name": "previous_id", + "description": "The unique ID of the user before", + "isOptional": true, + "type": "Number" + }, + { + "name": "distinct_id", + "description": "The current unique id", + "isOptional": true, + "type": "str" + }, + { + "name": "timestamp", + "description": "Optional timestamp for the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC.", + "isOptional": false, + "type": "datetime" + }, + { + "name": "uuid", + "description": "Optional UUID for the event", + "isOptional": true, + "type": "str" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup", + "isOptional": true, + "type": "bool" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Alias user", + "code": "# Alias user\nfrom posthog import alias\nalias(previous_id='distinct_id', distinct_id='alias_id')" + } + ] + }, + { + "id": "capture", + "title": "capture", + "description": "Capture anything a user does within your system.", + "details": "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. A capture call requires an event name to specify the event. We recommend using [verb] [noun], like `movie played` or `movie updated` to easily identify what your events mean later on. Capture takes a number of optional arguments, which are defined by the `OptionalCaptureArgs` type.", + "category": "Events", + "params": [ + { + "name": "event", + "description": "The event name to specify the event **kwargs: Optional arguments including:", + "isOptional": true, + "type": "str" + }, + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalCaptureArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Context and capture usage", + "code": "# Context and capture usage\nfrom posthog import new_context, identify_context, tag_context, capture\n# Enter a new context (e.g. a request/response cycle, an instance of a background job, etc)\nwith new_context():\n # Associate this context with some user, by distinct_id\n identify_context('some user')\n\n # Capture an event, associated with the context-level distinct ID ('some user')\n capture('movie started')\n\n # Capture an event associated with some other user (overriding the context-level distinct ID)\n capture('movie joined', distinct_id='some-other-user')\n\n # Capture an event with some properties\n capture('movie played', properties={'movie_id': '123', 'category': 'romcom'})\n\n # Capture an event with some properties\n capture('purchase', properties={'product_id': '123', 'category': 'romcom'})\n # Capture an event with some associated group\n capture('purchase', groups={'company': 'id:5'})\n\n # Adding a tag to the current context will cause it to appear on all subsequent events\n tag_context('some-tag', 'some-value')\n\n capture('another-event') # Will be captured with `'some-tag': 'some-value'` in the properties dict" + }, + { + "id": "example_2", + "name": "Set event properties", + "code": "# Set event properties\nfrom posthog import capture\ncapture(\n \"user_signed_up\",\n distinct_id=\"distinct_id_of_the_user\",\n properties={\n \"login_type\": \"email\",\n \"is_free_trial\": \"true\"\n }\n)" + } + ] + }, + { + "id": "capture_ai", + "title": "capture_ai", + "description": "Capture an AI event on the dedicated AI capture endpoint. Beta: the signature is stable; operational limits (per-event size cap, batching, endpoint) may change without notice. Takes the same arguments and returns the same value as `capture()`: the event UUID, or None when the event was not admitted (disabled client, or dropped by `before_send`). The event is delivered on an isolated queue with its own consumer pool and a higher per-event size cap, posting to the dedicated AI ingestion endpoint. The payload is sent as given \u2014 no redaction or truncation is applied here.", + "details": "", + "category": "Events", + "params": [ + { + "name": "event", + "description": "The event name, normally one of the `$ai_*` event names. **kwargs: Same optional arguments as `capture()`.", + "isOptional": true, + "type": "str" + }, + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalCaptureArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import capture_ai\n\nuuid = capture_ai(\n \"$ai_generation\",\n distinct_id=\"user_123\",\n properties={\"$ai_model\": \"gpt-5\"},\n)" + } + ] + }, + { + "id": "capture_exception", + "title": "capture_exception", + "description": "Capture exceptions that happen in your code.", + "details": "Capture exception is idempotent - if it is called twice with the same exception instance, only a occurrence will be tracked in posthog. This is because, generally, contexts will cause exceptions to be captured automatically. However, to ensure you track an exception, if you catch and do not re-raise it, capturing it manually is recommended, unless you are certain it will have crossed a context boundary (e.g. by existing a `with posthog.new_context():` block already). If the passed exception was raised and caught, the captured stack trace will consist of every frame between where the exception was raised and the point at which it is captured (the \"traceback\"). If the passed exception was never raised, e.g. if you call `posthog.capture_exception(ValueError(\"Some Error\"))`, the stack trace captured will be the full stack trace at the moment the exception was captured. Note that heavy use of contexts will lead to truncated stack traces, as the exception will be captured by the context entered most recently, 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. `capture_exception` takes the same set of optional arguments as `capture`.", + "category": "Events", + "params": [ + { + "name": "exception", + "description": "The exception to capture. If not provided, the current exception is captured via `sys.exc_info()` **kwargs: Optional capture arguments including distinct_id, properties, timestamp, uuid, groups, flags, send_feature_flags, and disable_geoip. Overriding reserved exception properties through ``properties`` is deprecated and will stop working in the next major version.", + "isOptional": false, + "type": "BaseException" + }, + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalCaptureArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Capture exception", + "code": "# Capture exception\nfrom posthog import capture_exception\ntry:\n risky_operation()\nexcept Exception as e:\n capture_exception(e)" + } + ] + }, + { + "id": "evaluate_flags", + "title": "evaluate_flags", + "description": "Evaluate all feature flags for a user in a single call and return a :class:`FeatureFlagEvaluations` snapshot. Branch on ``.is_enabled()`` / ``.get_flag()`` and pass the same snapshot to ``capture()`` via the ``flags`` option so events carry the exact flag values the code branched on. Prefer this over repeated ``get_feature_flag()`` calls and over ``capture(send_feature_flags=True)`` \u2014 it consolidates flag evaluation into a single ``/flags`` request per incoming request.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The user's distinct ID. If ``None``, falls back to the context distinct_id. If still unresolvable, returns an empty snapshot.", + "isOptional": false, + "type": "Number" + }, + { + "name": "groups", + "description": "Mapping of group type to group key.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "Person properties to use for evaluation.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "Group properties keyed by group type.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "If ``True``, never fall back to remote evaluation and omit flags that cannot be evaluated locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup.", + "isOptional": true, + "type": "bool" + }, + { + "name": "flag_keys", + "description": "Optional list that scopes local evaluation, the underlying ``/flags`` request, and the returned snapshot. When omitted or ``None``, all flags are evaluated. An empty list returns an empty snapshot without evaluating flags. A requested key absent from loaded local definitions is included in one remote fallback per ``evaluate_flags`` call unless ``only_evaluate_locally`` is ``True``. If the server also does not know the key, it is omitted from the snapshot.", + "isOptional": true, + "type": "list[str]" + }, + { + "name": "device_id", + "description": "Optional device ID override. If not provided, falls back to the context device_id (which may be set via tracing headers). Used by experience-continuity flags to match users across distinct_id changes.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "FeatureFlagEvaluations" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import evaluate_flags, capture\nflags = evaluate_flags(\"user_123\", person_properties={\"plan\": \"enterprise\"})\nif flags.is_enabled(\"new-dashboard\"):\n render_new_dashboard()\ncapture(\"page_viewed\", distinct_id=\"user_123\", flags=flags)" + } + ] + }, + { + "id": "feature_enabled", + "title": "feature_enabled", + "description": "Use feature flags to enable or disable features for users.", + "details": "You can call `posthog.load_feature_flags()` before to make sure you're not doing unexpected requests.", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "The feature flag key", + "isOptional": true, + "type": "str" + }, + { + "name": "distinct_id", + "description": "The user's distinct ID", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "Groups mapping", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "Person properties", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "Group properties", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to evaluate only locally", + "isOptional": false, + "type": "bool" + }, + { + "name": "send_feature_flag_events", + "description": "Whether to send feature flag events", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "Optional device ID override for experience-continuity flags", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[bool]" + }, + "examples": [ + { + "id": "example_1", + "name": "Boolean feature flag", + "code": "# Boolean feature flag\nfrom posthog import feature_enabled, get_feature_flag_payload\nis_my_flag_enabled = feature_enabled('flag-key', 'distinct_id_of_your_user')\nif is_my_flag_enabled:\n matched_flag_payload = get_feature_flag_payload('flag-key', 'distinct_id_of_your_user')" + } + ] + }, + { + "id": "feature_flag_definitions", + "title": "feature_flag_definitions", + "description": "Returns loaded feature flags.", + "details": "Returns loaded feature flags, if any. Helpful for debugging what flag information you have loaded.", + "category": "Feature flags", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import feature_flag_definitions\ndefinitions = feature_flag_definitions()" + } + ] + }, + { + "id": "flush", + "title": "flush", + "description": "Attempt immediate delivery of queued events and ended spans, keeping the global client reusable. Call ``flush()`` before a short-lived request or serverless invocation returns when using the shared module-level client. Use ``shutdown()`` only for final process cleanup or a client that will not be reused. Flush does not stop the background workers or feature flag poller, and does not end open spans. Returning does not guarantee server receipt: pending work can remain after a timeout or span export failure, and failed events may be dropped by consumers. Calls made directly from SDK callbacks such as ``on_error`` are deferred rather than blocking the worker that invoked the callback.", + "details": "", + "category": "Client management", + "params": [ + { + "name": "timeout_seconds", + "description": "Seconds to wait for event queues to drain, shared across event lanes. Defaults to 10 seconds. Pass ``None`` to wait without a time limit; this does not guarantee successful delivery. Queued spans flush concurrently, waiting up to this many seconds for an in-flight flush before starting a fresh drain budget. A lock wait timeout leaves the in-flight flush to finish. At least one span request is attempted after acquiring the lock, even with no budget left; retriable failures are retried after backoff while budget remains, with one last attempt at the deadline. The last span request is not cut short and is bounded by the client's ``timeout`` in seconds, so this is not a strict wall-clock limit for the whole call. With ``None``, a retriable span failure is left for a later flush rather than retried within this call.", + "isOptional": true, + "type": "float" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "import posthog\nposthog.capture('event_name', distinct_id='user_id')\n# Before a serverless invocation returns; keep the global client open.\nposthog.flush(timeout_seconds=10)" + } + ] + }, + { + "id": "get_active_span", + "title": "get_active_span", + "description": "The span that is active in the current context, or ``None``. Alpha. Only entering a span (``with posthog.start_span(...) as span:``) makes it active; a span started manually is not. Use it to propagate the trace to the next service: ``span.traceparent()`` is the header value.", + "details": "", + "category": "Tracing", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[Span]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "span = posthog.get_active_span()\nif span is not None:\n headers[\"traceparent\"] = span.traceparent()" + } + ] + }, + { + "id": "get_all_flags", + "title": "get_all_flags", + "description": "Get all flags for a given user.", + "details": "Flags are key-value pairs where the key is the flag key and the value is the flag variant, or True, or False.", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The user's distinct ID", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "Groups mapping", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "Person properties", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "Group properties", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to evaluate only locally", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "Optional device ID override for experience-continuity flags", + "isOptional": true, + "type": "str" + }, + { + "name": "flag_keys_to_evaluate", + "description": "Optional list of flag keys to evaluate (evaluates all if None)", + "isOptional": true, + "type": "list[str]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[dict[str, Union[bool, str]]]" + }, + "examples": [ + { + "id": "example_1", + "name": "All flags for user", + "code": "# All flags for user\nfrom posthog import get_all_flags\nget_all_flags('distinct_id_of_your_user')" + } + ] + }, + { + "id": "get_all_flags_and_payloads", + "title": "get_all_flags_and_payloads", + "description": "Get all feature flag values and payloads for a user.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The user's distinct ID.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "Mapping of group type to group key.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "Person properties to use for evaluation.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "Group properties keyed by group type.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to evaluate only locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup.", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "Optional device ID override for experience-continuity flags.", + "isOptional": true, + "type": "str" + }, + { + "name": "flag_keys_to_evaluate", + "description": "Optional list of flag keys to evaluate. Evaluates all flags when omitted.", + "isOptional": true, + "type": "list[str]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "FlagsAndPayloads" + } + }, + { + "id": "get_feature_flag", + "title": "get_feature_flag", + "description": "Get feature flag variant for users. Used with experiments.", + "details": "`groups` are a mapping from group type to group key. So, if you have a group type of \"organization\" and a group key of \"5\", you would pass groups={\"organization\": \"5\"}. `group_properties` take the format: { group_type_name: { group_properties } }. So, for example, if you have the group type \"organization\" and the group key \"5\", with the properties name, and employee count, you'll send these as: group_properties={\"organization\": {\"name\": \"PostHog\", \"employees\": 11}}.", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "The feature flag key", + "isOptional": true, + "type": "str" + }, + { + "name": "distinct_id", + "description": "The user's distinct ID", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "Groups mapping from group type to group key", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "Person properties", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "Group properties in format { group_type_name: { group_properties } }", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to evaluate only locally", + "isOptional": false, + "type": "bool" + }, + { + "name": "send_feature_flag_events", + "description": "Whether to send feature flag events", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "Optional device ID override for experience-continuity flags", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Union[bool, str, any]" + }, + "examples": [ + { + "id": "example_1", + "name": "Multivariate feature flag", + "code": "# Multivariate feature flag\nfrom posthog import get_feature_flag, get_feature_flag_payload\nenabled_variant = get_feature_flag('flag-key', 'distinct_id_of_your_user')\nif enabled_variant == 'variant-key':\n matched_flag_payload = get_feature_flag_payload('flag-key', 'distinct_id_of_your_user')" + } + ] + }, + { + "id": "get_feature_flag_evaluation_runtime", + "title": "get_feature_flag_evaluation_runtime", + "description": "Return where a locally loaded feature flag is meant to be evaluated.", + "details": "Reads the `evaluation_runtime` each flag definition carries, so no extra request is made. Returns `None` when local evaluation has not loaded a definition for this key. A definition that carries no runtime reports `FeatureFlagEvaluationRuntime.ALL`, the default PostHog applies.", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[FeatureFlagEvaluationRuntime]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import FeatureFlagEvaluationRuntime, get_feature_flag_evaluation_runtime\nruntime = get_feature_flag_evaluation_runtime(\"my-flag\")" + } + ] + }, + { + "id": "get_feature_flag_keys_by_evaluation_runtime", + "title": "get_feature_flag_keys_by_evaluation_runtime", + "description": "Return the keys of locally loaded flags that a runtime can evaluate.", + "details": "A flag set to `FeatureFlagEvaluationRuntime.ALL` suits either runtime, so it is returned for `CLIENT` and for `SERVER`. Use this to decide which flags to hand to a browser when a backend serves flags to its own frontend.", + "category": "Feature flags", + "params": [ + { + "name": "evaluation_runtime", + "description": "", + "isOptional": true, + "type": "FeatureFlagEvaluationRuntime" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "list[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import FeatureFlagEvaluationRuntime, get_feature_flag_keys_by_evaluation_runtime\nclient_keys = get_feature_flag_keys_by_evaluation_runtime(FeatureFlagEvaluationRuntime.CLIENT)" + } + ] + }, + { + "id": "get_feature_flag_payload", + "title": "get_feature_flag_payload", + "description": "Get the payload associated with a feature flag value. Deprecated for new code. Prefer ``evaluate_flags()`` and ``flags.get_flag_payload(key)`` so flag evaluation happens once per request.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "The feature flag key.", + "isOptional": true, + "type": "str" + }, + { + "name": "distinct_id", + "description": "The user's distinct ID.", + "isOptional": true, + "type": "Number" + }, + { + "name": "match_value", + "description": "Optional flag value to use when selecting a payload.", + "isOptional": false, + "type": "bool" + }, + { + "name": "groups", + "description": "Mapping of group type to group key.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "Person properties to use for evaluation.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "Group properties keyed by group type.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to evaluate only locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "send_feature_flag_events", + "description": "Whether to send a $feature_flag_called event.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup.", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "Optional device ID override for experience-continuity flags.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[object]" + } + }, + { + "id": "get_feature_flag_result", + "title": "get_feature_flag_result", + "description": "Get a FeatureFlagResult object which contains the flag result and payload. This method evaluates a feature flag and returns a FeatureFlagResult object containing: - enabled: Whether the flag is enabled - variant: The variant value if the flag has variants - payload: The payload associated with the flag (automatically deserialized from JSON) - key: The flag key - reason: Why the flag was enabled/disabled", + "details": "", + "category": null, + "params": [ + { + "name": "key", + "description": "The feature flag key.", + "isOptional": true, + "type": "str" + }, + { + "name": "distinct_id", + "description": "The user's distinct ID.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "Mapping of group type to group key.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "Person properties to use for evaluation.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "Group properties keyed by group type.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to evaluate only locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "send_feature_flag_events", + "description": "Whether to send a $feature_flag_called event.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup.", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "Optional device ID override for experience-continuity flags.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[FeatureFlagResult]" + } + }, + { + "id": "get_remote_config_payload", + "title": "get_remote_config_payload", + "description": "Get the payload for a remote config feature flag.", + "details": "", + "category": null, + "params": [ + { + "name": "key", + "description": "The key of the feature flag", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "get_tags", + "title": "get_tags", + "description": "Get all tags from the current context. Returns: Dict of all tags in the current context", + "details": "", + "category": "Contexts", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "dict[str, Any]" + } + }, + { + "id": "group_identify", + "title": "group_identify", + "description": "Set properties on a group.", + "details": "", + "category": "Identification", + "params": [ + { + "name": "group_type", + "description": "Type of your group. Required - the call is dropped with a warning if it is missing or empty.", + "isOptional": true, + "type": "str" + }, + { + "name": "group_key", + "description": "Unique identifier of the group. Required - the call is dropped with a warning if it is missing or empty.", + "isOptional": true, + "type": "str" + }, + { + "name": "properties", + "description": "Properties to set on the group", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "timestamp", + "description": "Optional timestamp for the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC.", + "isOptional": false, + "type": "datetime" + }, + { + "name": "uuid", + "description": "Optional UUID for the event", + "isOptional": true, + "type": "str" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup", + "isOptional": true, + "type": "bool" + }, + { + "name": "distinct_id", + "description": "Optional distinct ID of the user performing the action", + "isOptional": false, + "type": "Number" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Group identify", + "code": "# Group identify\nfrom posthog import group_identify\ngroup_identify('company', 'company_id_in_your_db', {\n 'name': 'Awesome Inc.',\n 'employees': 11\n})" + } + ] + }, + { + "id": "identify_context", + "title": "identify_context", + "description": "Identify the current context with a distinct ID.", + "details": "", + "category": "Identification", + "params": [ + { + "name": "distinct_id", + "description": "The distinct ID to associate with the current context and its children", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import identify_context\nidentify_context(\"user_123\")" + } + ] + }, + { + "id": "join", + "title": "join", + "description": "Attempt to process queued events and stop the client's background workers. Use `shutdown()` directly in most cases. Failed or undrainable events may be dropped and reported through logging or ``on_error``; returning does not guarantee server receipt. Lifecycle cleanup is attempted once, and cleanup failures are logged without retry.", + "details": "", + "category": "Client management", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import join\njoin()" + } + ] + }, + { + "id": "load_feature_flags", + "title": "load_feature_flags", + "description": "Load feature flag definitions from PostHog.", + "details": "", + "category": "Feature flags", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import load_feature_flags\nload_feature_flags()" + } + ] + }, + { + "id": "new_context", + "title": "new_context", + "description": "Create a new context scope that will be active for the duration of the with block.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "fresh", + "description": "Whether to start with a fresh context (default: False)", + "isOptional": false, + "type": "bool" + }, + { + "name": "capture_exceptions", + "description": "Whether to capture exceptions raised within the context. If omitted, defaults to the relevant client's exception autocapture setting.", + "isOptional": true, + "type": "bool" + }, + { + "name": "client", + "description": "Optional Posthog client instance to use for this context (default: None)", + "isOptional": true, + "type": "Client" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import new_context, tag, capture\nwith new_context():\n tag(\"request_id\", \"123\")\n capture(\"event_name\", properties={\"property\": \"value\"})" + } + ] + }, + { + "id": "scoped", + "title": "scoped", + "description": "Decorator that creates a new context for the function.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "fresh", + "description": "Whether to start with a fresh context (default: False)", + "isOptional": false, + "type": "bool" + }, + { + "name": "capture_exceptions", + "description": "Whether to capture and track exceptions with posthog error tracking. If omitted, defaults to the global exception autocapture setting.", + "isOptional": true, + "type": "bool" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import scoped, tag, capture\n@scoped()\ndef process_payment(payment_id):\n tag(\"payment_id\", payment_id)\n capture(\"payment_started\")" + } + ] + }, + { + "id": "set", + "title": "set", + "description": "Set properties on a user record.", + "details": "This will overwrite previous people property values. Generally operates similar to `capture`, with distinct_id being an optional argument, defaulting to the current context's distinct ID. If there is no context-level distinct ID, and no override distinct_id is passed, this function will do nothing. Context tags are folded into $set properties, so tagging the current context and then calling `set` will cause those tags to be set on the user (unlike capture, which causes them to just be set on the event).", + "category": "Identification", + "params": [ + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalSetArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Set person properties", + "code": "# Set person properties\nfrom posthog import set\nset(distinct_id='distinct_id', properties={'name': 'Max Hedgehog'})" + } + ] + }, + { + "id": "set_capture_exception_code_variables_context", + "title": "set_capture_exception_code_variables_context", + "description": "Override code-variable capture for exceptions in the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "enabled", + "description": "Whether exceptions captured in this context should include local variable values from stack frames.", + "isOptional": true, + "type": "bool" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "set_code_variables_detect_secrets_context", + "title": "set_code_variables_detect_secrets_context", + "description": "Whether to apply entropy-based secret detection as a last-resort redaction of high-entropy values (API keys, tokens, strong passwords) in captured code variables for the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "enabled", + "description": "", + "isOptional": true, + "type": "bool" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "set_code_variables_ignore_patterns_context", + "title": "set_code_variables_ignore_patterns_context", + "description": "Override code-variable ignore patterns for exceptions in the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "ignore_patterns", + "description": "Variable-name patterns that should be omitted entirely when code variables are captured.", + "isOptional": true, + "type": "list[str]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "set_code_variables_mask_patterns_context", + "title": "set_code_variables_mask_patterns_context", + "description": "Override code-variable mask patterns for exceptions in the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "mask_patterns", + "description": "Variable-name patterns whose values should be replaced with ``***`` when code variables are captured.", + "isOptional": true, + "type": "list[str]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "set_code_variables_mask_url_credentials_context", + "title": "set_code_variables_mask_url_credentials_context", + "description": "Whether to scrub credentials embedded in URLs/DSNs (e.g. user:pass@host) from captured code variables for the current context.", + "details": "", + "category": null, + "params": [ + { + "name": "enabled", + "description": "", + "isOptional": true, + "type": "bool" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "set_context_device_id", + "title": "set_context_device_id", + "description": "Set the device ID for the current context, associating all feature flag requests in this or child contexts with the given device ID.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "device_id", + "description": "The device ID to associate with the current context and its children", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import set_context_device_id\nset_context_device_id(\"device_123\")" + } + ] + }, + { + "id": "set_context_session", + "title": "set_context_session", + "description": "Set the session ID for the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "session_id", + "description": "The session ID to associate with the current context and its children", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import set_context_session\nset_context_session(\"session_123\")" + } + ] + }, + { + "id": "set_once", + "title": "set_once", + "description": "Set properties on a user record, only if they do not yet exist.", + "details": "This will not overwrite previous people property values, unlike `set`. Otherwise, operates in an identical manner to `set`.", + "category": "Identification", + "params": [ + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalSetArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Set property once", + "code": "# Set property once\nfrom posthog import set_once\nset_once(distinct_id='distinct_id', properties={'initial_url': '/blog'})" + } + ] + }, + { + "id": "setup", + "title": "setup", + "description": "Create or return the global PostHog client configured by module settings. Most applications should either instantiate ``Posthog`` directly or set ``posthog.api_key``/other module settings before calling top-level helpers. ``setup()`` is called automatically by global APIs such as ``capture()``. Returns: The global ``Client`` instance. If both ``api_key`` and ``project_api_key`` are missing or blank, the client is disabled and module-level calls become no-ops.", + "details": "", + "category": "Initialization", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Client" + } + }, + { + "id": "shutdown", + "title": "shutdown", + "description": "Attempt final delivery and permanently shut down the global client. Call this for final process cleanup, not at the end of each request or serverless invocation that shares the module-level client. Use ``flush()`` before returning from those invocations instead. Shutdown closes capture lanes, stops background workers and the feature flag poller, and tears down integrations. The global client is not recreated automatically after shutdown. This normally blocks until queued events have been attempted and cleanup finishes. Failed or undrainable events may be dropped and reported through logging or ``on_error``; returning does not guarantee server receipt. Event draining has no timeout. Queued spans get a final flush with up to 30 seconds waiting for an in-flight flush, then a fresh 30-second drain budget; the last HTTP request is not cut short. Unsent queued spans and still-open spans are discarded with a warning. Lifecycle cleanup is attempted once, and cleanup failures are logged without retry. Calls made directly from SDK callbacks such as ``on_error`` are deferred to avoid deadlocking the worker. If blocking completion is required, signal an application-owned thread, return from the callback, and call ``shutdown()`` from that thread. Do not wait inside a callback for another thread or task calling a lifecycle method.", + "details": "", + "category": "Client management", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import shutdown\nshutdown()" + } + ] + }, + { + "id": "start_span", + "title": "start_span", + "description": "Start a span for distributed tracing. Alpha. Returns a span handle. Use it as a context manager to make it the active span for the block and end it on exit (recording a raised exception on the way out); or call ``end()`` yourself for a span that cannot wrap a block. Spans started inside the block nest under it automatically. Always returns a usable handle, even when tracing is off, so calling code never branches.", + "details": "", + "category": "Tracing", + "params": [ + { + "name": "name", + "description": "A low-cardinality operation name, e.g. ``GET /users/:id``. Variable values belong in attributes, not the name.", + "isOptional": true, + "type": "str" + }, + { + "name": "kind", + "description": "``internal`` (default), ``server``, ``client``, ``producer`` or ``consumer``.", + "isOptional": true, + "type": "str" + }, + { + "name": "attributes", + "description": "Initial attributes.", + "isOptional": true, + "type": "Mapping[str, Any]" + }, + { + "name": "parent", + "description": "A span handle, or an inbound W3C ``traceparent`` header value to continue a remote trace. Defaults to the active span.", + "isOptional": false, + "type": "Span" + }, + { + "name": "tracestate", + "description": "The inbound ``tracestate`` header accompanying a ``traceparent`` string ``parent``; preserved and propagated.", + "isOptional": true, + "type": "str" + }, + { + "name": "start_time", + "description": "A ``datetime`` or epoch seconds, to backdate the span.", + "isOptional": false, + "type": "datetime" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Span" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "import posthog\nposthog.traces = {\"service_name\": \"checkout-api\"}\n\nwith posthog.start_span(\"POST /checkout\", parent=request.headers.get(\"traceparent\")) as span:\n span.set_attribute(\"plan\", user.plan)\n with posthog.start_span(\"db.query\", kind=\"client\"):\n ...\n outgoing_headers = {\"traceparent\": span.traceparent()}" + } + ] + }, + { + "id": "tag", + "title": "tag", + "description": "Add a tag to the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "name", + "description": "The tag key", + "isOptional": true, + "type": "str" + }, + { + "name": "value", + "description": "The tag value", + "isOptional": true, + "type": "Any" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import tag\ntag(\"user_id\", \"123\")" + } + ] + } + ] + } + ], + "categories": [ + "Initialization", + "Identification", + "Capture", + "Error Tracking", + "Feature flags", + "Tracing", + "Contexts", + "Events", + "Client management" + ] +} \ No newline at end of file diff --git a/references/posthog-python-references-latest.json b/references/posthog-python-references-latest.json index bacd9eaf..3ced9019 100644 --- a/references/posthog-python-references-latest.json +++ b/references/posthog-python-references-latest.json @@ -2,7 +2,7 @@ "id": "posthog-python", "hogRef": "0.3", "info": { - "version": "7.75.8", + "version": "7.75.9", "id": "posthog-python", "title": "PostHog Python SDK", "description": "Integrate PostHog into any python application.", From 066e186d1f336dceb5b00e21d44aaec12b9de9e8 Mon Sep 17 00:00:00 2001 From: alexcastgonz <82875882+pcbeingused333@users.noreply.github.com> Date: Fri, 23 Oct 2026 08:42:33 -0400 Subject: [PATCH 13/60] fix(ai): record the HTTP status of google-genai errors (#1110) * fix(ai): record the HTTP status of google-genai errors The generation and embedding wrappers read the HTTP status of a failed call from the exception's status_code, which OpenAI and Anthropic errors have. google-genai's APIError (ClientError, ServerError) carries it as code, so every failed Gemini call was captured with $ai_http_status 0. Read either attribute through a small helper; a code outside the HTTP range still counts as no status. Co-Authored-By: Claude Sonnet 5.5 * fix: remove unused import from HTTP status tests --------- Co-authored-by: Claude Sonnet 5.5 Co-authored-by: Manoel Aranda Neto (cherry picked from commit 25d2767a70378da02c321879f0fa3a2e52d071b7) --- .sampo/changesets/gemini-error-http-status.md | 5 + posthog/ai/gemini/_shared.py | 5 +- posthog/ai/utils.py | 23 +++- posthog/test/ai/test_http_status.py | 124 ++++++++++++++++++ 4 files changed, 148 insertions(+), 9 deletions(-) create mode 100644 .sampo/changesets/gemini-error-http-status.md create mode 100644 posthog/test/ai/test_http_status.py diff --git a/.sampo/changesets/gemini-error-http-status.md b/.sampo/changesets/gemini-error-http-status.md new file mode 100644 index 00000000..4cc01a50 --- /dev/null +++ b/.sampo/changesets/gemini-error-http-status.md @@ -0,0 +1,5 @@ +--- +pypi/posthog: patch +--- + +Failed Gemini calls are now captured with the HTTP status of the error as `$ai_http_status` (for example 429 or 503) instead of 0. google-genai errors carry the status as `code` rather than `status_code`, which the OpenAI and Anthropic wrappers already read. diff --git a/posthog/ai/gemini/_shared.py b/posthog/ai/gemini/_shared.py index 31900b40..9c8e7c8e 100644 --- a/posthog/ai/gemini/_shared.py +++ b/posthog/ai/gemini/_shared.py @@ -16,6 +16,7 @@ _resolve_ai_client, capture_streaming_event, finalize_ai_content, + _get_http_status, merge_system_prompt, with_privacy_mode, ) @@ -243,9 +244,7 @@ def _capture_embedding_outcome( privacy_mode, finalize_ai_content(contents, self._ph_client), ), - "$ai_http_status": ( - getattr(error, "status_code", 0) if error is not None else 200 - ), + "$ai_http_status": (_get_http_status(error) if error is not None else 200), # Omitted when the provider never reported a count: absent means # unknown, 0 is a report of nothing. **({"$ai_input_tokens": input_tokens} if input_tokens is not None else {}), diff --git a/posthog/ai/utils.py b/posthog/ai/utils.py index bb193242..595a58ad 100644 --- a/posthog/ai/utils.py +++ b/posthog/ai/utils.py @@ -488,6 +488,21 @@ def merge_system_prompt( return [] +def _get_http_status(exc: BaseException) -> int: + """HTTP status of an SDK exception, 0 when it has none. + + OpenAI and Anthropic errors carry `status_code`; google-genai's `APIError` + carries the status as `code`. + """ + status = getattr(exc, "status_code", None) + if isinstance(status, int): + return status + code = getattr(exc, "code", None) + if isinstance(code, int) and not isinstance(code, bool) and 100 <= code <= 599: + return code + return 0 + + def call_llm_and_track_usage( posthog_distinct_id: Optional[str], ph_client: PostHogClient, @@ -519,9 +534,7 @@ def call_llm_and_track_usage( response = call_method(**kwargs) except Exception as exc: error = exc - http_status = getattr( - exc, "status_code", 0 - ) # default to 0 becuase its likely an SDK error + http_status = _get_http_status(exc) # 0 because its likely an SDK error error_params = { "$ai_is_error": True, "$ai_error": exc.__str__(), @@ -691,9 +704,7 @@ async def call_llm_and_track_usage_async( raise except Exception as exc: error = exc - http_status = getattr( - exc, "status_code", 0 - ) # default to 0 because its likely an SDK error + http_status = _get_http_status(exc) # 0 because its likely an SDK error error_params = { "$ai_is_error": True, "$ai_error": exc.__str__(), diff --git a/posthog/test/ai/test_http_status.py b/posthog/test/ai/test_http_status.py new file mode 100644 index 00000000..0e9f56c9 --- /dev/null +++ b/posthog/test/ai/test_http_status.py @@ -0,0 +1,124 @@ +"""Failed provider calls are recorded with the HTTP status of the SDK error.""" + +from unittest.mock import patch + +import pytest + +from posthog.ai.utils import ( + call_llm_and_track_usage, + call_llm_and_track_usage_async, + _get_http_status, +) + + +@pytest.fixture +def mock_client(): + with patch("posthog.client.Client") as mock_client: + mock_client.privacy_mode = False + yield mock_client + + +class _WithStatusCode(Exception): + status_code = 503 + + +class _WithCode(Exception): + def __init__(self, code): + super().__init__("boom") + self.code = code + + +def test_get_http_status(): + assert _get_http_status(_WithStatusCode()) == 503 + assert _get_http_status(_WithCode(429)) == 429 + # A `code` that is not an HTTP status is not one + assert _get_http_status(_WithCode(2)) == 0 + assert _get_http_status(_WithCode("RESOURCE_EXHAUSTED")) == 0 + assert _get_http_status(RuntimeError("no status")) == 0 + + +def _genai_errors(): + errors = pytest.importorskip("google.genai.errors") + return errors + + +def _gemini_error(errors, cls, code): + return cls(code, {"error": {"code": code, "message": "nope", "status": "ERROR"}}) + + +@pytest.mark.parametrize( + ("cls_name", "code"), [("ClientError", 429), ("ServerError", 503)] +) +def test_gemini_api_error_status_is_recorded(mock_client, cls_name, code): + errors = _genai_errors() + err = _gemini_error(errors, getattr(errors, cls_name), code) + + def boom(**kwargs): + raise err + + with pytest.raises(getattr(errors, cls_name)): + call_llm_and_track_usage( + "user", + mock_client, + "gemini", + None, + None, + False, + None, + "https://generativelanguage.googleapis.com", + boom, + model="gemini-3.8-flash", + contents="hi", + ) + + props = mock_client.capture_ai.call_args[1]["properties"] + assert props["$ai_is_error"] is True + assert props["$ai_http_status"] == code + + +@pytest.mark.asyncio +async def test_gemini_api_error_status_is_recorded_async(mock_client): + errors = _genai_errors() + err = _gemini_error(errors, errors.ClientError, 429) + + async def boom(**kwargs): + raise err + + with pytest.raises(errors.ClientError): + await call_llm_and_track_usage_async( + "user", + mock_client, + "gemini", + None, + None, + False, + None, + "https://generativelanguage.googleapis.com", + boom, + model="gemini-3.8-flash", + contents="hi", + ) + + props = mock_client.capture_ai.call_args[1]["properties"] + assert props["$ai_http_status"] == 429 + + +def test_status_code_errors_keep_their_status(mock_client): + def boom(**kwargs): + raise _WithStatusCode("overloaded") + + with pytest.raises(_WithStatusCode): + call_llm_and_track_usage( + "user", + mock_client, + "openai", + None, + None, + False, + None, + "https://x", + boom, + model="m", + ) + + assert mock_client.capture_ai.call_args[1]["properties"]["$ai_http_status"] == 503 From 88686efdafd4df106c9fa013ff3022f58ed07086 Mon Sep 17 00:00:00 2001 From: "releaser-posthog-python[bot]" <262414804+releaser-posthog-python[bot]@users.noreply.github.com> Date: Fri, 23 Oct 2026 12:44:06 +0000 Subject: [PATCH 14/60] chore: Release v7.75.10 [skip ci] (cherry picked from commit 82f948b0871ecbbf5c6fd30714481dc5877adbf1) --- .sampo/changesets/gemini-error-http-status.md | 5 ----- openfeature-provider/CHANGELOG.md | 6 ++++++ openfeature-provider/pyproject.toml | 2 +- posthog/CHANGELOG.md | 6 ++++++ posthog/version.py | 2 +- pyproject.toml | 2 +- uv.lock | 4 ++-- 7 files changed, 17 insertions(+), 10 deletions(-) delete mode 100644 .sampo/changesets/gemini-error-http-status.md diff --git a/.sampo/changesets/gemini-error-http-status.md b/.sampo/changesets/gemini-error-http-status.md deleted file mode 100644 index 4cc01a50..00000000 --- a/.sampo/changesets/gemini-error-http-status.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -pypi/posthog: patch ---- - -Failed Gemini calls are now captured with the HTTP status of the error as `$ai_http_status` (for example 429 or 503) instead of 0. google-genai errors carry the status as `code` rather than `status_code`, which the OpenAI and Anthropic wrappers already read. diff --git a/openfeature-provider/CHANGELOG.md b/openfeature-provider/CHANGELOG.md index db08dcd7..4ba230b6 100644 --- a/openfeature-provider/CHANGELOG.md +++ b/openfeature-provider/CHANGELOG.md @@ -4,6 +4,12 @@ All notable changes to `openfeature-provider-posthog` are documented here. This file is maintained by [Sampo](https://github.com/bruits/sampo) from changesets in `.sampo/changesets/` that target `pypi/openfeature-provider-posthog`. +## 0.1.124 — 2026-10-23 + +### Patch changes + +- Updated dependencies: posthog@7.75.10 + ## 0.1.123 — 2026-10-23 ### Patch changes diff --git a/openfeature-provider/pyproject.toml b/openfeature-provider/pyproject.toml index b1df3502..4819f434 100644 --- a/openfeature-provider/pyproject.toml +++ b/openfeature-provider/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "openfeature-provider-posthog" -version = "0.1.123" +version = "0.1.124" description = "Official PostHog provider for the OpenFeature Python SDK." readme = "README.md" authors = [{ name = "PostHog", email = "engineering@posthog.com" }] diff --git a/posthog/CHANGELOG.md b/posthog/CHANGELOG.md index 5a6bf681..0b165616 100644 --- a/posthog/CHANGELOG.md +++ b/posthog/CHANGELOG.md @@ -1,5 +1,11 @@ # posthog +## 7.75.10 — 2026-10-23 + +### Patch changes + +- [25d2767](https://github.com/posthog/posthog-python/commit/25d2767a70378da02c321879f0fa3a2e52d071b7) Failed Gemini calls are now captured with the HTTP status of the error as `$ai_http_status` (for example 429 or 503) instead of 0. google-genai errors carry the status as `code` rather than `status_code`, which the OpenAI and Anthropic wrappers already read. — Thanks @pcbeingused333! + ## 7.75.9 — 2026-10-23 ### Patch changes diff --git a/posthog/version.py b/posthog/version.py index 50da243a..80d27f16 100644 --- a/posthog/version.py +++ b/posthog/version.py @@ -1 +1 @@ -VERSION = "7.75.9" +VERSION = "7.75.10" diff --git a/pyproject.toml b/pyproject.toml index cba52e44..d9934210 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "posthog" -version = "7.75.9" +version = "7.75.10" description = "Integrate PostHog into any python application." authors = [{ name = "PostHog", email = "engineering@posthog.com" }] maintainers = [{ name = "PostHog", email = "engineering@posthog.com" }] diff --git a/uv.lock b/uv.lock index 52928cf7..96dd88a1 100644 --- a/uv.lock +++ b/uv.lock @@ -2656,7 +2656,7 @@ wheels = [ [[package]] name = "openfeature-provider-posthog" -version = "0.1.123" +version = "0.1.124" source = { editable = "openfeature-provider" } dependencies = [ { name = "openfeature-sdk" }, @@ -3015,7 +3015,7 @@ wheels = [ [[package]] name = "posthog" -version = "7.75.9" +version = "7.75.10" source = { editable = "." } dependencies = [ { name = "distro" }, From a11af04110dd38816a2023caf899778ab60a7c6a Mon Sep 17 00:00:00 2001 From: "releaser-posthog-python[bot]" <262414804+releaser-posthog-python[bot]@users.noreply.github.com> Date: Fri, 23 Oct 2026 12:44:11 +0000 Subject: [PATCH 15/60] Update generated references (cherry picked from commit b0a2783b53e7a8e5546f11674318225b26139ec4) --- .../posthog-python-references-7.75.10.json | 3548 +++++++++++++++++ .../posthog-python-references-latest.json | 2 +- 2 files changed, 3549 insertions(+), 1 deletion(-) create mode 100644 references/posthog-python-references-7.75.10.json diff --git a/references/posthog-python-references-7.75.10.json b/references/posthog-python-references-7.75.10.json new file mode 100644 index 00000000..226ca01c --- /dev/null +++ b/references/posthog-python-references-7.75.10.json @@ -0,0 +1,3548 @@ +{ + "id": "posthog-python", + "hogRef": "0.3", + "info": { + "version": "7.75.10", + "id": "posthog-python", + "title": "PostHog Python SDK", + "description": "Integrate PostHog into any python application.", + "slugPrefix": "posthog-python", + "specUrl": "https://github.com/PostHog/posthog-python" + }, + "types": [ + { + "id": "FeatureFlag", + "name": "FeatureFlag", + "path": "posthog.types.FeatureFlag", + "properties": [ + { + "name": "key", + "type": "str", + "description": "Field: key" + }, + { + "name": "enabled", + "type": "bool", + "description": "Field: enabled" + }, + { + "name": "variant", + "type": "Optional[str]", + "description": "Field: variant" + }, + { + "name": "reason", + "type": "Optional[FlagReason]", + "description": "Field: reason" + }, + { + "name": "metadata", + "type": "Union[FlagMetadata, LegacyFlagMetadata]", + "description": "Field: metadata" + } + ], + "example": "" + }, + { + "id": "FeatureFlagResult", + "name": "FeatureFlagResult", + "path": "posthog.types.FeatureFlagResult", + "properties": [ + { + "name": "key", + "type": "str", + "description": "Field: key" + }, + { + "name": "enabled", + "type": "bool", + "description": "Field: enabled" + }, + { + "name": "variant", + "type": "Optional[str]", + "description": "Field: variant" + }, + { + "name": "payload", + "type": "Optional[Any]", + "description": "Field: payload" + }, + { + "name": "reason", + "type": "Optional[str]", + "description": "Field: reason" + } + ], + "example": "" + }, + { + "id": "FlagMetadata", + "name": "FlagMetadata", + "path": "posthog.types.FlagMetadata", + "properties": [ + { + "name": "id", + "type": "int", + "description": "Field: id" + }, + { + "name": "payload", + "type": "Optional[str]", + "description": "Field: payload" + }, + { + "name": "version", + "type": "int", + "description": "Field: version" + }, + { + "name": "description", + "type": "str", + "description": "Field: description" + }, + { + "name": "has_experiment", + "type": "Optional[bool]", + "description": "Field: has_experiment" + } + ], + "example": "" + }, + { + "id": "FlagReason", + "name": "FlagReason", + "path": "posthog.types.FlagReason", + "properties": [ + { + "name": "code", + "type": "str", + "description": "Field: code" + }, + { + "name": "condition_index", + "type": "Optional[int]", + "description": "Field: condition_index" + }, + { + "name": "description", + "type": "str", + "description": "Field: description" + } + ], + "example": "" + }, + { + "id": "FlagsAndPayloads", + "name": "FlagsAndPayloads", + "path": "posthog.types.FlagsAndPayloads", + "properties": [ + { + "name": "featureFlags", + "type": "Optional[dict[str, Union[bool, str]]]", + "description": "Field: featureFlags" + }, + { + "name": "featureFlagPayloads", + "type": "Optional[dict[str, Any]]", + "description": "Field: featureFlagPayloads" + } + ], + "example": "" + }, + { + "id": "FlagsResponse", + "name": "FlagsResponse", + "path": "posthog.types.FlagsResponse", + "properties": [ + { + "name": "flags", + "type": "dict[str, FeatureFlag]", + "description": "Field: flags" + }, + { + "name": "errorsWhileComputingFlags", + "type": "bool", + "description": "Field: errorsWhileComputingFlags" + }, + { + "name": "requestId", + "type": "str", + "description": "Field: requestId" + }, + { + "name": "quotaLimit", + "type": "Optional[list[str]]", + "description": "Field: quotaLimit" + }, + { + "name": "evaluatedAt", + "type": "Optional[int]", + "description": "Field: evaluatedAt" + }, + { + "name": "minimalFlagCalledEvents", + "type": "bool", + "description": "Field: minimalFlagCalledEvents" + } + ], + "example": "" + }, + { + "id": "LegacyFlagMetadata", + "name": "LegacyFlagMetadata", + "path": "posthog.types.LegacyFlagMetadata", + "properties": [ + { + "name": "payload", + "type": "Any", + "description": "Field: payload" + } + ], + "example": "" + }, + { + "id": "SendFeatureFlagsOptions", + "name": "SendFeatureFlagsOptions", + "path": "posthog.types.SendFeatureFlagsOptions", + "properties": [ + { + "name": "should_send", + "type": "bool", + "description": "Field: should_send" + }, + { + "name": "only_evaluate_locally", + "type": "Optional[bool]", + "description": "Field: only_evaluate_locally" + }, + { + "name": "person_properties", + "type": "Optional[dict[str, Any]]", + "description": "Field: person_properties" + }, + { + "name": "group_properties", + "type": "Optional[dict[str, dict[str, Any]]]", + "description": "Field: group_properties" + }, + { + "name": "flag_keys_filter", + "type": "Optional[list[str]]", + "description": "Field: flag_keys_filter" + } + ], + "example": "" + }, + { + "id": "OptionalCaptureArgs", + "name": "OptionalCaptureArgs", + "path": "posthog.args.OptionalCaptureArgs", + "properties": [ + { + "name": "distinct_id", + "type": "NotRequired[Union[Number, str, UUID, int, any]]", + "description": "Field: distinct_id" + }, + { + "name": "properties", + "type": "NotRequired[Optional[dict[str, Any]]]", + "description": "Field: properties" + }, + { + "name": "timestamp", + "type": "NotRequired[Union[datetime, str, any]]", + "description": "Field: timestamp" + }, + { + "name": "uuid", + "type": "NotRequired[Union[str, UUID, any]]", + "description": "Field: uuid" + }, + { + "name": "groups", + "type": "NotRequired[Optional[dict[str, str]]]", + "description": "Field: groups" + }, + { + "name": "flags", + "type": "NotRequired[Optional[ForwardRef('FeatureFlagEvaluations')]]", + "description": "Field: flags" + }, + { + "name": "send_feature_flags", + "type": "NotRequired[Union[bool, SendFeatureFlagsOptions, any]]", + "description": "Field: send_feature_flags" + }, + { + "name": "disable_geoip", + "type": "NotRequired[Optional[bool]]", + "description": "Field: disable_geoip" + }, + { + "name": "_property_allowlist", + "type": "NotRequired[Optional[frozenset[str]]]", + "description": "Field: _property_allowlist" + } + ], + "example": "" + }, + { + "id": "OptionalSetArgs", + "name": "OptionalSetArgs", + "path": "posthog.args.OptionalSetArgs", + "properties": [ + { + "name": "distinct_id", + "type": "NotRequired[Union[Number, str, UUID, int, any]]", + "description": "Field: distinct_id" + }, + { + "name": "properties", + "type": "NotRequired[Optional[dict[str, Any]]]", + "description": "Field: properties" + }, + { + "name": "timestamp", + "type": "NotRequired[Union[datetime, str, any]]", + "description": "Field: timestamp" + }, + { + "name": "uuid", + "type": "NotRequired[Union[str, UUID, any]]", + "description": "Field: uuid" + }, + { + "name": "disable_geoip", + "type": "NotRequired[Optional[bool]]", + "description": "Field: disable_geoip" + } + ], + "example": "" + }, + { + "id": "SendFeatureFlagsOptions", + "name": "SendFeatureFlagsOptions", + "path": "posthog.types.SendFeatureFlagsOptions", + "properties": [ + { + "name": "should_send", + "type": "bool", + "description": "Field: should_send" + }, + { + "name": "only_evaluate_locally", + "type": "Optional[bool]", + "description": "Field: only_evaluate_locally" + }, + { + "name": "person_properties", + "type": "Optional[dict[str, Any]]", + "description": "Field: person_properties" + }, + { + "name": "group_properties", + "type": "Optional[dict[str, dict[str, Any]]]", + "description": "Field: group_properties" + }, + { + "name": "flag_keys_filter", + "type": "Optional[list[str]]", + "description": "Field: flag_keys_filter" + } + ], + "example": "" + } + ], + "classes": [ + { + "id": "PostHog", + "title": "PostHog", + "description": "This is the SDK reference for the PostHog Python SDK. You can learn more about example usage in the [Python SDK documentation](/docs/libraries/python). You can also follow [Flask](/docs/libraries/flask) and [Django](/docs/libraries/django) guides to integrate PostHog into your project. For long-running applications, create one client during application startup and reuse it for the lifetime of the process. This keeps background queues predictable and makes shutdown flushing straightforward. Multiple clients are still supported for intentional multi-project or multi-host setups.", + "functions": [ + { + "id": "__init__", + "title": "Client", + "description": "Initialize a new PostHog client instance.", + "details": "", + "category": "Initialization", + "params": [ + { + "name": "project_api_key", + "description": "PostHog project API key/token.", + "isOptional": true, + "type": "str" + }, + { + "name": "host", + "description": "PostHog host. Defaults to the US ingestion endpoint when not set. App hosts such as ``https://us.posthog.com`` are mapped to the corresponding ingestion host.", + "isOptional": false, + "type": "any" + }, + { + "name": "debug", + "description": "Enable verbose SDK logging and re-raise errors from public API methods.", + "isOptional": false, + "type": "bool" + }, + { + "name": "max_queue_size", + "description": "Maximum number of events buffered before upload.", + "isOptional": false, + "type": "int" + }, + { + "name": "send", + "description": "If False, queueing succeeds but events are not sent.", + "isOptional": false, + "type": "bool" + }, + { + "name": "on_error", + "description": "Optional callback invoked by background consumers when an upload fails. Keep it short and non-blocking. Calling lifecycle methods directly is safe and deferred, but do not start another thread or task that calls ``flush()``, ``join()``, or ``shutdown()`` and then wait for it from the callback.", + "isOptional": false, + "type": "any" + }, + { + "name": "flush_at", + "description": "Number of queued events that triggers a batch upload.", + "isOptional": false, + "type": "int" + }, + { + "name": "flush_interval", + "description": "Maximum seconds a background consumer waits before flushing a partial batch.", + "isOptional": false, + "type": "float" + }, + { + "name": "gzip", + "description": "Whether to gzip event upload payloads.", + "isOptional": false, + "type": "bool" + }, + { + "name": "max_retries", + "description": "Number of upload retries. Values below 0 are treated as 0.", + "isOptional": false, + "type": "int" + }, + { + "name": "sync_mode", + "description": "If True, send each event synchronously instead of using background worker threads. This blocks the calling thread; in asyncio applications such as FastAPI, use ``AsyncPosthog`` instead.", + "isOptional": false, + "type": "bool" + }, + { + "name": "timeout", + "description": "HTTP request timeout in seconds for event uploads.", + "isOptional": false, + "type": "float" + }, + { + "name": "thread", + "description": "Number of background consumer threads.", + "isOptional": false, + "type": "int" + }, + { + "name": "poll_interval", + "description": "Seconds between local feature flag definition refreshes.", + "isOptional": false, + "type": "int" + }, + { + "name": "personal_api_key", + "description": "Deprecated alias for ``secret_key``. Still honored for backwards compatibility; prefer ``secret_key``, which also accepts a Project Secret API Key.", + "isOptional": false, + "type": "any" + }, + { + "name": "disabled", + "description": "If True, disable captures and API requests. Useful in tests.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable server-side GeoIP enrichment. Defaults to True.", + "isOptional": false, + "type": "bool" + }, + { + "name": "is_server", + "description": "Whether events are emitted from a server-side runtime. Defaults to True; set to False when using the SDK as a client/CLI so the device OS is attributed to the person normally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "historical_migration", + "description": "Mark events as historical migration imports.", + "isOptional": false, + "type": "bool" + }, + { + "name": "feature_flags_request_timeout_seconds", + "description": "Timeout in seconds for feature flag and remote config requests.", + "isOptional": false, + "type": "float" + }, + { + "name": "feature_flags_request_max_retries", + "description": "Number of retries for feature flag requests after network, transport, or timeout failures. Defaults to 1. Set to 0 to disable retries.", + "isOptional": false, + "type": "int" + }, + { + "name": "super_properties", + "description": "Properties merged into every captured event.", + "isOptional": false, + "type": "any" + }, + { + "name": "enable_exception_autocapture", + "description": "Automatically capture uncaught exceptions.", + "isOptional": false, + "type": "bool" + }, + { + "name": "log_captured_exceptions", + "description": "Also log exceptions captured by error tracking.", + "isOptional": false, + "type": "bool" + }, + { + "name": "project_root", + "description": "Root path used to determine in-app stack frames for captured exceptions. Defaults to the current working directory.", + "isOptional": false, + "type": "any" + }, + { + "name": "privacy_mode", + "description": "For AI observability, capture usage metadata without prompt inputs or outputs.", + "isOptional": false, + "type": "bool" + }, + { + "name": "before_send", + "description": "Optional callback that can modify or drop events before upload. Return ``None`` to drop an event.", + "isOptional": false, + "type": "any" + }, + { + "name": "flag_fallback_cache_url", + "description": "Optional feature flag fallback cache URL, such as ``memory://local/?ttl=300&size=10000`` or a Redis URL.", + "isOptional": false, + "type": "any" + }, + { + "name": "enable_local_evaluation", + "description": "Whether to poll feature flag definitions for local evaluation when a personal API key is configured.", + "isOptional": false, + "type": "bool" + }, + { + "name": "flag_definition_cache_provider", + "description": "Optional external cache provider for sharing feature flag definitions across workers.", + "isOptional": true, + "type": "FlagDefinitionCacheProvider" + }, + { + "name": "capture_exception_code_variables", + "description": "Capture local variable values on exception stack frames.", + "isOptional": false, + "type": "bool" + }, + { + "name": "code_variables_mask_patterns", + "description": "Variable-name patterns to mask when capturing code variables.", + "isOptional": false, + "type": "any" + }, + { + "name": "code_variables_ignore_patterns", + "description": "Variable-name patterns to omit when capturing code variables.", + "isOptional": false, + "type": "any" + }, + { + "name": "code_variables_mask_url_credentials", + "description": "Scrub credentials embedded in URLs/DSNs (e.g. ``user:pass@host``) from captured code variables, regardless of the surrounding variable name. Defaults to True.", + "isOptional": false, + "type": "any" + }, + { + "name": "code_variables_detect_secrets", + "description": "Last-resort entropy-based detection that redacts high-entropy secret-looking values (API keys, tokens, strong passwords) sitting in innocuously-named variables, after the name and URL checks. Skips structured ids (UUIDs, ObjectIds, hashes). Defaults to True.", + "isOptional": false, + "type": "any" + }, + { + "name": "in_app_modules", + "description": "Module/package prefixes treated as in-app frames in captured exceptions.", + "isOptional": false, + "type": "UnionType[list[str], any]" + }, + { + "name": "enable_exception_autocapture_rate_limiting", + "description": "Rate limit autocaptured exceptions client-side with a token bucket per exception type. Disabled by default.", + "isOptional": false, + "type": "bool" + }, + { + "name": "exception_autocapture_bucket_size", + "description": "Maximum burst of autocaptured exceptions allowed per exception type (token bucket size, clamped to 0-100).", + "isOptional": false, + "type": "int" + }, + { + "name": "exception_autocapture_refill_rate", + "description": "Tokens restored per refill interval for each exception type's bucket.", + "isOptional": false, + "type": "int" + }, + { + "name": "exception_autocapture_refill_interval_seconds", + "description": "Seconds between token refills for autocaptured exception rate limiting.", + "isOptional": false, + "type": "int" + }, + { + "name": "capture_mode", + "description": "Capture wire protocol to use. Defaults to ``CaptureMode.V0`` (legacy ``/batch/``). Set ``CaptureMode.V1`` (or pass the string ``\"v1\"``) to opt into ``/i/v1/analytics/events``. When omitted, the ``POSTHOG_CAPTURE_MODE`` env var is consulted, then ``V0``.", + "isOptional": false, + "type": "CaptureMode" + }, + { + "name": "capture_compression", + "description": "Request-body compression for capture-v1 uploads (ignored in V0, which uses ``gzip``). ``CaptureCompression.GZIP`` or ``DEFLATE`` (or the strings ``\"gzip\"``/``\"deflate\"``). When omitted, the ``POSTHOG_CAPTURE_COMPRESSION`` env var is consulted, then the legacy ``gzip`` flag, then no compression.", + "isOptional": false, + "type": "CaptureCompression" + }, + { + "name": "secret_key", + "description": "A Personal API Key or Project Secret API Key, used to authenticate local feature flag evaluation, remote config payloads, and decrypted flag payloads. Example:: posthog.Client(project_api_key, secret_key=\"phx_...\")", + "isOptional": false, + "type": "any" + }, + { + "name": "metrics", + "description": "", + "isOptional": true, + "type": "dict" + }, + { + "name": "enable_full_ai_capture", + "description": "Route PostHog AI wrapper events through the dedicated AI capture endpoint and capture full AI content: skips string truncation and passes media (base64/data URIs) through unredacted. ``privacy_mode`` always wins. Defaults to False.", + "isOptional": false, + "type": "bool" + }, + { + "name": "capture_trace_context", + "description": "When OpenTelemetry is installed and a valid span is active at capture time, add its trace and span IDs as ``$trace_id`` and ``$span_id`` properties to events captured with ``capture()`` and ``capture_ai()``, so they can be correlated with backend traces. Explicit ``$trace_id``/``$span_id`` values passed in ``properties`` win. Exception events (``capture_exception``) always attach these IDs regardless of this setting. Defaults to False.", + "isOptional": false, + "type": "bool" + }, + { + "name": "_use_ai_lane", + "description": "", + "isOptional": false, + "type": "bool" + }, + { + "name": "_enable_multimodal_capture", + "description": "", + "isOptional": false, + "type": "bool" + }, + { + "name": "traces", + "description": "Config dict for distributed tracing: ``service_name``, ``service_version``, ``environment``, ``resource_attributes``, ``flush_interval`` (5 s), ``max_queue_size`` (2048), ``max_export_batch_size`` (512), ``max_live_spans`` (10000), ``max_span_age`` (3600 s), ``max_attributes_per_span`` (128), ``max_events_per_span`` (128), ``max_attribute_value_length`` (8192). ``before_span_send`` is a callable, or a list run in order, that receives each finished span as a dict (``trace_id``, ``span_id`` and ``parent_span_id`` are read-only) and returns it, edited, or ``None`` to drop it; a hook that raises drops the span. Tracing is off until this is provided. Spans export on a background timer even with ``sync_mode``; serverless handlers should call ``flush()`` before returning. Defaults to None.", + "isOptional": true, + "type": "dict" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import Posthog\n\nposthog = Posthog('', host='')" + } + ] + }, + { + "id": "alias", + "title": "alias", + "description": "Create an alias between two distinct IDs.", + "details": "", + "category": "Identification", + "params": [ + { + "name": "previous_id", + "description": "The previous distinct ID. Required - the call is dropped with a warning if it is missing or empty.", + "isOptional": true, + "type": "Number" + }, + { + "name": "distinct_id", + "description": "The new distinct ID to alias to. Falls back to the context distinct ID; the call is dropped with a warning if neither is available.", + "isOptional": true, + "type": "str" + }, + { + "name": "timestamp", + "description": "The timestamp of the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC.", + "isOptional": false, + "type": "datetime" + }, + { + "name": "uuid", + "description": "A unique identifier for the event. If provided, it must be a valid UUID string or uuid.UUID instance; invalid values are ignored and replaced with a newly generated UUID.", + "isOptional": true, + "type": "str" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this event.", + "isOptional": true, + "type": "bool" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.alias(previous_id='distinct_id', distinct_id='alias_id')" + } + ] + }, + { + "id": "capture", + "title": "capture", + "description": "Captures an event manually. [Learn about capture best practices](https://posthog.com/docs/product-analytics/capture-events)", + "details": "", + "category": "Capture", + "params": [ + { + "name": "event", + "description": "The event name to capture.", + "isOptional": true, + "type": "str" + }, + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalCaptureArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Anonymous event", + "code": "# Anonymous event\nposthog.capture('some-anon-event')" + }, + { + "id": "example_2", + "name": "Context usage", + "code": "# Context usage\nfrom posthog import identify_context, new_context\nwith new_context():\n identify_context('distinct_id_of_the_user')\n posthog.capture('user_signed_up')\n posthog.capture('user_logged_in')\n posthog.capture('some-custom-action', distinct_id='distinct_id_of_the_user')" + }, + { + "id": "example_3", + "name": "Set event properties", + "code": "# Set event properties\nposthog.capture(\n \"user_signed_up\",\n distinct_id=\"distinct_id_of_the_user\",\n properties={\n \"login_type\": \"email\",\n \"is_free_trial\": \"true\"\n }\n)" + }, + { + "id": "example_4", + "name": "Page view event", + "code": "# Page view event\nposthog.capture('$pageview', distinct_id=\"distinct_id_of_the_user\", properties={'$current_url': 'https://example.com'})" + } + ] + }, + { + "id": "capture_ai", + "title": "capture_ai", + "description": "Capture an AI event on the dedicated AI capture endpoint. Beta: the signature is stable; operational limits (per-event size cap, batching, endpoint) may change without notice. Takes the same arguments and returns the same value as `capture()`: the event UUID, or None when the event was not admitted (disabled client, or dropped by `before_send`). The event is queued on an isolated AI lane with its own consumer pool and a higher per-event size cap, posting to the dedicated AI ingestion endpoint. The payload is sent as given \u2014 no redaction or truncation is applied here.", + "details": "", + "category": "Capture", + "params": [ + { + "name": "event", + "description": "", + "isOptional": true, + "type": "str" + }, + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalCaptureArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + } + }, + { + "id": "capture_exception", + "title": "capture_exception", + "description": "Capture an exception for error tracking. When OpenTelemetry is installed and a valid span is active, its trace and span IDs are added as ``$trace_id`` and ``$span_id`` event properties.", + "details": "", + "category": "Error Tracking", + "params": [ + { + "name": "exception", + "description": "The exception to capture.", + "isOptional": true, + "type": "BaseException" + }, + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalCaptureArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "try:\n # Some code that might fail\n pass\nexcept Exception as e:\n posthog.capture_exception(e, 'user_distinct_id', properties=additional_properties)" + } + ] + }, + { + "id": "evaluate_flags", + "title": "evaluate_flags", + "description": "Evaluate all feature flags for a user in a single call and return a :class:`FeatureFlagEvaluations` snapshot. Branch on ``.is_enabled()`` / ``.get_flag()`` and pass the same snapshot to :meth:`capture` via the ``flags`` option so events carry the exact flag values the code branched on. Prefer this over repeated ``get_feature_flag()`` calls and over ``capture(send_feature_flags=True)`` \u2014 it consolidates flag evaluation into a single ``/flags`` request per incoming request. Local evaluation is transparent: when the poller resolves a flag, the snapshot's ``$feature_flag_called`` events are tagged ``locally_evaluated=True`` and reason ``\"Evaluated locally\"``.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The user's distinct ID. If ``None``, falls back to the context distinct_id. If still unresolvable, returns an empty snapshot.", + "isOptional": false, + "type": "Number" + }, + { + "name": "groups", + "description": "Mapping of group type to group key.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "Person properties to use for evaluation.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "Group properties keyed by group type.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "If True, never fall back to remote evaluation \u2014 flags that can't be evaluated locally are simply omitted from the snapshot.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup.", + "isOptional": true, + "type": "bool" + }, + { + "name": "flag_keys", + "description": "Optional list that scopes local evaluation, the underlying ``/flags`` request, and the returned snapshot. When omitted or ``None``, all flags are evaluated. An empty list returns an empty snapshot without evaluating flags. A requested key absent from loaded local definitions is included in one remote fallback per ``evaluate_flags`` call unless ``only_evaluate_locally`` is True. If the server also does not know the key, it is omitted from the snapshot.", + "isOptional": true, + "type": "list[str]" + }, + { + "name": "device_id", + "description": "Optional device ID override. If not provided, falls back to the context device_id (which may be set via tracing headers). Used by experience-continuity flags to match users across distinct_id changes.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "FeatureFlagEvaluations" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "flags = posthog.evaluate_flags(\n \"user_123\",\n person_properties={\"plan\": \"enterprise\"},\n)\nif flags.is_enabled(\"new-dashboard\"):\n render_new_dashboard()\nposthog.capture(\"page_viewed\", distinct_id=\"user_123\", flags=flags)" + } + ] + }, + { + "id": "feature_enabled", + "title": "feature_enabled", + "description": "Check if a feature flag is enabled for a user.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "The feature flag key.", + "isOptional": true, + "type": "str" + }, + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to only evaluate locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "send_feature_flag_events", + "description": "Whether to send feature flag events.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[bool]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "is_my_flag_enabled = posthog.feature_enabled('flag-key', 'distinct_id_of_your_user')\nif is_my_flag_enabled:\n # Do something differently for this user\n # Optional: fetch the payload\n matched_flag_payload = posthog.get_feature_flag_payload('flag-key', 'distinct_id_of_your_user')" + } + ] + }, + { + "id": "feature_flag_definitions", + "title": "feature_flag_definitions", + "description": "Return feature flag definitions loaded for local evaluation. Returns: The currently loaded feature flag definitions, or ``None`` before local evaluation has loaded definitions.", + "details": "", + "category": "Feature flags", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "flush", + "title": "flush", + "description": "Attempt immediate delivery of queued events and ended spans, keeping the client reusable. For a client shared across requests or serverless invocations (including the module-level client), call ``flush()`` before a short-lived invocation returns. Use ``shutdown()`` instead only for final process cleanup or when disposing of a client that will not be reused. Flush does not stop the background workers or feature flag poller, and does not end open spans. Returning does not guarantee server receipt: pending work can remain after a timeout or span export failure, and failed events may be dropped by the consumers. Calls made directly from SDK callbacks such as ``on_error`` are deferred rather than blocking the worker that invoked the callback.", + "details": "", + "category": null, + "params": [ + { + "name": "timeout_seconds", + "description": "Seconds to wait for event queues to drain, shared across event lanes. Defaults to 10 seconds. Pass ``None`` to wait without a time limit; this does not guarantee successful delivery. Queued spans flush concurrently. The span exporter waits up to this many seconds for an in-flight flush, then uses a fresh drain budget once it acquires the lock. If the lock wait times out, the in-flight flush is left to finish. At least one span request is attempted after acquiring the lock, even with no budget left; retriable failures are retried after backoff while budget remains, with one last attempt at the deadline. Each span request is bounded by the client's ``timeout`` in seconds, and the last request is not cut short. This is therefore not a strict wall-clock limit for the whole call. With ``None``, a retriable span failure is left for a later flush rather than retried within this call.", + "isOptional": true, + "type": "float" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.capture('event_name', distinct_id='user_id')\n# Before a serverless invocation returns; keep the shared client open.\nposthog.flush(timeout_seconds=10)" + } + ] + }, + { + "id": "get_active_span", + "title": "get_active_span", + "description": "The span that is active in the current context, or ``None``. Alpha. Only entering a span (``with posthog.start_span(...) as span:``) makes it active; a span started manually is not. Use it to propagate the trace to the next service: ``span.traceparent()`` is the header value.", + "details": "", + "category": "Tracing", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[Span]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "span = posthog.get_active_span()\nif span is not None:\n headers[\"traceparent\"] = span.traceparent()" + } + ] + }, + { + "id": "get_all_flags", + "title": "get_all_flags", + "description": "Get all feature flags for a user.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to only evaluate locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "flag_keys_to_evaluate", + "description": "A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance.", + "isOptional": true, + "type": "list[str]" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[dict[str, Union[bool, str]]]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.get_all_flags('distinct_id_of_your_user')" + } + ] + }, + { + "id": "get_all_flags_and_payloads", + "title": "get_all_flags_and_payloads", + "description": "Get all feature flags and their payloads for a user.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to only evaluate locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "flag_keys_to_evaluate", + "description": "A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance.", + "isOptional": true, + "type": "list[str]" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "FlagsAndPayloads" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.get_all_flags_and_payloads('distinct_id_of_your_user')" + } + ] + }, + { + "id": "get_feature_flag", + "title": "get_feature_flag", + "description": "Get multivariate feature flag value for a user.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "The feature flag key.", + "isOptional": true, + "type": "str" + }, + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to only evaluate locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "send_feature_flag_events", + "description": "Whether to send feature flag events.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Union[bool, str, any]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "enabled_variant = posthog.get_feature_flag('flag-key', 'distinct_id_of_your_user')\nif enabled_variant == 'variant-key': # replace 'variant-key' with the key of your variant\n # Do something differently for this user\n # Optional: fetch the payload\n matched_flag_payload = posthog.get_feature_flag_payload('flag-key', 'distinct_id_of_your_user')" + } + ] + }, + { + "id": "get_feature_flag_evaluation_runtime", + "title": "get_feature_flag_evaluation_runtime", + "description": "Return where a locally loaded feature flag is meant to be evaluated.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "The feature flag key.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[FeatureFlagEvaluationRuntime]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import FeatureFlagEvaluationRuntime\n\nruntime = posthog.get_feature_flag_evaluation_runtime(\"my-flag\")\nif runtime is FeatureFlagEvaluationRuntime.SERVER:\n ..." + } + ] + }, + { + "id": "get_feature_flag_keys_by_evaluation_runtime", + "title": "get_feature_flag_keys_by_evaluation_runtime", + "description": "Return the keys of locally loaded flags that a runtime can evaluate. A flag set to ``FeatureFlagEvaluationRuntime.ALL`` suits either runtime, so it is returned for ``CLIENT`` and for ``SERVER``, and asking for ``ALL`` returns every loaded flag. Use this to decide which flags to hand to a browser when a backend serves flags to its own frontend.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "evaluation_runtime", + "description": "The runtime to match, as a ``FeatureFlagEvaluationRuntime`` or its string value.", + "isOptional": true, + "type": "FeatureFlagEvaluationRuntime" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "list[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import FeatureFlagEvaluationRuntime\n\nclient_keys = posthog.get_feature_flag_keys_by_evaluation_runtime(\n FeatureFlagEvaluationRuntime.CLIENT\n)" + } + ] + }, + { + "id": "get_feature_flag_payload", + "title": "get_feature_flag_payload", + "description": "Get the payload for a feature flag.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "The feature flag key.", + "isOptional": true, + "type": "str" + }, + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "match_value", + "description": "The specific flag value to get payload for.", + "isOptional": false, + "type": "bool" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to only evaluate locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "send_feature_flag_events", + "description": "Deprecated. Use get_feature_flag() instead if you need events.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[object]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "is_my_flag_enabled = posthog.feature_enabled('flag-key', 'distinct_id_of_your_user')\n\nif is_my_flag_enabled:\n # Do something differently for this user\n # Optional: fetch the payload\n matched_flag_payload = posthog.get_feature_flag_payload('flag-key', 'distinct_id_of_your_user')" + } + ] + }, + { + "id": "get_feature_flag_result", + "title": "get_feature_flag_result", + "description": "Get a FeatureFlagResult object which contains the flag result and payload for a key by evaluating locally or remotely depending on whether local evaluation is enabled and the flag can be locally evaluated. This also captures the `$feature_flag_called` event unless `send_feature_flag_events` is `False`.", + "details": "", + "category": null, + "params": [ + { + "name": "key", + "description": "The feature flag key.", + "isOptional": true, + "type": "str" + }, + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to only evaluate locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "send_feature_flag_events", + "description": "Whether to send feature flag events.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[FeatureFlagResult]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "flag_result = posthog.get_feature_flag_result('flag-key', 'distinct_id_of_your_user')\nif flag_result and flag_result.get_value() == 'variant-key':\n # Do something differently for this user\n # Optional: fetch the payload\n matched_flag_payload = flag_result.payload" + } + ] + }, + { + "id": "get_feature_flags_and_payloads", + "title": "get_feature_flags_and_payloads", + "description": "Get feature flags and payloads for a user.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "flag_keys_to_evaluate", + "description": "A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance.", + "isOptional": true, + "type": "list[str]" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "FlagsAndPayloads" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "result = posthog.get_feature_flags_and_payloads('')" + } + ] + }, + { + "id": "get_feature_payloads", + "title": "get_feature_payloads", + "description": "Get feature flag payloads for a user, preserving valid serialized JSON.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "flag_keys_to_evaluate", + "description": "A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance.", + "isOptional": true, + "type": "list[str]" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "dict[str, Optional[str]]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "payloads = posthog.get_feature_payloads('')" + } + ] + }, + { + "id": "get_feature_variants", + "title": "get_feature_variants", + "description": "Get feature flag variants for a user.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "flag_keys_to_evaluate", + "description": "A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance.", + "isOptional": true, + "type": "list[str]" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "dict[str, Union[bool, str]]" + } + }, + { + "id": "get_flags_decision", + "title": "get_flags_decision", + "description": "Get feature flags decision.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": false, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "flag_keys_to_evaluate", + "description": "A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance.", + "isOptional": true, + "type": "list[str]" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "FlagsResponse" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "decision = posthog.get_flags_decision('user123')" + } + ] + }, + { + "id": "get_remote_config_payload", + "title": "get_remote_config_payload", + "description": "Get the payload for a remote config feature flag.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "The remote config feature flag key.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "get_tags", + "title": "get_tags", + "description": "Get all tags from the current context. Returns: Dict of all tags in the current context.", + "details": "", + "category": "Contexts", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "dict[str, Any]" + } + }, + { + "id": "group_identify", + "title": "group_identify", + "description": "Identify a group and set its properties.", + "details": "", + "category": "Identification", + "params": [ + { + "name": "group_type", + "description": "The type of group (e.g., 'company', 'team'). Required - the call is dropped with a warning if it is missing or empty.", + "isOptional": true, + "type": "str" + }, + { + "name": "group_key", + "description": "The unique identifier for the group. Required - the call is dropped with a warning if it is missing or empty.", + "isOptional": true, + "type": "str" + }, + { + "name": "properties", + "description": "A dictionary of properties to set on the group.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "timestamp", + "description": "The timestamp of the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC.", + "isOptional": false, + "type": "datetime" + }, + { + "name": "uuid", + "description": "A unique identifier for the event. If provided, it must be a valid UUID string or uuid.UUID instance; invalid values are ignored and replaced with a newly generated UUID.", + "isOptional": false, + "type": "str" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this event.", + "isOptional": true, + "type": "bool" + }, + { + "name": "distinct_id", + "description": "The distinct ID of the user performing the action.", + "isOptional": false, + "type": "Number" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.group_identify('company', 'company_id_in_your_db', {\n 'name': 'Awesome Inc.',\n 'employees': 11\n})" + } + ] + }, + { + "id": "identify_context", + "title": "identify_context", + "description": "Identify the current context with a distinct ID.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "distinct_id", + "description": "The distinct ID to associate with the current context and its children.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + } + }, + { + "id": "join", + "title": "join", + "description": "Attempt to process queued events and end the consumer threads. Do not use directly, call `shutdown()` instead. Failed or undrainable events may be dropped and reported through logging or ``on_error``; returning does not guarantee server receipt. Lifecycle cleanup is attempted once, and cleanup failures are logged without retry.", + "details": "", + "category": null, + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.join()" + } + ] + }, + { + "id": "load_feature_flags", + "title": "load_feature_flags", + "description": "Load feature flags for local evaluation.", + "details": "", + "category": "Feature flags", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.load_feature_flags()" + } + ] + }, + { + "id": "new_context", + "title": "new_context", + "description": "Create a new context for managing shared state. Learn more about [contexts](/docs/libraries/python#contexts).", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "fresh", + "description": "Whether to create a fresh context that doesn't inherit from parent.", + "isOptional": false, + "type": "bool" + }, + { + "name": "capture_exceptions", + "description": "Whether to automatically capture exceptions in this context. If omitted, defaults to this client's exception autocapture setting.", + "isOptional": true, + "type": "bool" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "with client.new_context():\n client.identify_context('')\n client.capture('event_name')" + } + ] + }, + { + "id": "scoped", + "title": "scoped", + "description": "Decorator that creates a new context for the wrapped function using this client.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "fresh", + "description": "Whether to create a fresh context that doesn't inherit from parent.", + "isOptional": false, + "type": "bool" + }, + { + "name": "capture_exceptions", + "description": "Whether to automatically capture exceptions in this context. If omitted, defaults to this client's exception autocapture setting.", + "isOptional": true, + "type": "bool" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "set", + "title": "set", + "description": "Set properties on a person profile.", + "details": "", + "category": "Identification", + "params": [ + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalSetArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Set with distinct id", + "code": "# Set with distinct id\nposthog.set(distinct_id='user123', properties={'name': 'Max Hedgehog'})" + } + ] + }, + { + "id": "set_context_device_id", + "title": "set_context_device_id", + "description": "Set the device ID for the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "device_id", + "description": "The device ID to associate with the current context and its children.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + } + }, + { + "id": "set_context_session", + "title": "set_context_session", + "description": "Set the session ID for the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "session_id", + "description": "The session ID to associate with the current context and its children.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + } + }, + { + "id": "set_once", + "title": "set_once", + "description": "Set properties on a person profile only if they haven't been set before.", + "details": "", + "category": "Identification", + "params": [ + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalSetArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.set_once(distinct_id='user123', properties={'initial_signup_date': '2024-01-01'})" + } + ] + }, + { + "id": "shutdown", + "title": "shutdown", + "description": "Attempt final delivery and permanently shut down the client. Call this for final process cleanup or when disposing of a client that will not be reused. It closes capture lanes, stops background workers and the feature flag poller, and tears down integrations. Do not call it at the end of each request or serverless invocation when the client is shared; call ``flush()`` before returning instead. A shut-down client cannot be reused; create a new client if needed. Normally this method blocks until queued events have been attempted and cleanup finishes. Failed or undrainable events may be dropped and reported through logging or ``on_error``; returning does not guarantee server receipt. Event draining has no timeout. Queued spans get one final flush with up to 30 seconds waiting for an in-flight flush, then a fresh 30-second drain budget; the last HTTP request is not cut short. Any spans it cannot send are discarded with a warning, as are spans still open. Lifecycle cleanup is attempted once, and cleanup failures are logged without retry. When called directly from an SDK callback such as ``on_error``, shutdown is deferred to avoid blocking the worker that invoked the callback. If the callback must coordinate a blocking shutdown, have it signal an application-owned thread and return before that thread calls shutdown. Do not wait inside the callback for another thread or task that calls a lifecycle method.", + "details": "", + "category": null, + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.shutdown()" + } + ] + }, + { + "id": "start_span", + "title": "start_span", + "description": "Start a span for distributed tracing. Alpha. Returns a span handle. Use it as a context manager to make it the active span for the block and end it on exit (recording a raised exception on the way out); or call ``end()`` yourself for a span that cannot wrap a block. Spans started inside the block nest under it automatically. Always returns a usable handle, even when tracing is off, so calling code never branches.", + "details": "", + "category": "Tracing", + "params": [ + { + "name": "name", + "description": "A low-cardinality operation name, e.g. ``GET /users/:id``. Variable values belong in attributes, not the name.", + "isOptional": true, + "type": "str" + }, + { + "name": "kind", + "description": "``internal`` (default), ``server``, ``client``, ``producer`` or ``consumer``.", + "isOptional": true, + "type": "str" + }, + { + "name": "attributes", + "description": "Initial attributes.", + "isOptional": true, + "type": "Mapping[str, Any]" + }, + { + "name": "parent", + "description": "A span handle, or an inbound W3C ``traceparent`` header value to continue a remote trace. Defaults to the active span. A forked child starts with no active span; pass the parent span to continue a trace across a fork.", + "isOptional": false, + "type": "Span" + }, + { + "name": "tracestate", + "description": "The inbound ``tracestate`` header accompanying a ``traceparent`` string ``parent``; preserved and propagated.", + "isOptional": true, + "type": "str" + }, + { + "name": "start_time", + "description": "A ``datetime`` or epoch seconds, to backdate the span.", + "isOptional": false, + "type": "datetime" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Span" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog = Posthog(\"\", traces={\"service_name\": \"checkout-api\"})\n\nwith posthog.start_span(\"POST /checkout\", parent=request.headers.get(\"traceparent\")) as span:\n span.set_attribute(\"plan\", user.plan)\n with posthog.start_span(\"db.query\", kind=\"client\"):\n ...\n outgoing_headers = {\"traceparent\": span.traceparent()}" + } + ] + }, + { + "id": "tag", + "title": "tag", + "description": "Add a tag to the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "name", + "description": "The tag key.", + "isOptional": true, + "type": "str" + }, + { + "name": "value", + "description": "The tag value.", + "isOptional": true, + "type": "Any" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + } + } + ] + }, + { + "id": "PostHogModule", + "title": "PostHog Module Functions", + "description": "Global functions available in the PostHog module", + "functions": [ + { + "id": "alias", + "title": "alias", + "description": "Associate user behaviour before and after they e.g. register, login, or perform some other identifying action.", + "details": "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?\". Particularly useful for associating user behaviour before and after they e.g. register, login, or perform some other identifying action.", + "category": "Identification", + "params": [ + { + "name": "previous_id", + "description": "The unique ID of the user before", + "isOptional": true, + "type": "Number" + }, + { + "name": "distinct_id", + "description": "The current unique id", + "isOptional": true, + "type": "str" + }, + { + "name": "timestamp", + "description": "Optional timestamp for the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC.", + "isOptional": false, + "type": "datetime" + }, + { + "name": "uuid", + "description": "Optional UUID for the event", + "isOptional": true, + "type": "str" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup", + "isOptional": true, + "type": "bool" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Alias user", + "code": "# Alias user\nfrom posthog import alias\nalias(previous_id='distinct_id', distinct_id='alias_id')" + } + ] + }, + { + "id": "capture", + "title": "capture", + "description": "Capture anything a user does within your system.", + "details": "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. A capture call requires an event name to specify the event. We recommend using [verb] [noun], like `movie played` or `movie updated` to easily identify what your events mean later on. Capture takes a number of optional arguments, which are defined by the `OptionalCaptureArgs` type.", + "category": "Events", + "params": [ + { + "name": "event", + "description": "The event name to specify the event **kwargs: Optional arguments including:", + "isOptional": true, + "type": "str" + }, + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalCaptureArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Context and capture usage", + "code": "# Context and capture usage\nfrom posthog import new_context, identify_context, tag_context, capture\n# Enter a new context (e.g. a request/response cycle, an instance of a background job, etc)\nwith new_context():\n # Associate this context with some user, by distinct_id\n identify_context('some user')\n\n # Capture an event, associated with the context-level distinct ID ('some user')\n capture('movie started')\n\n # Capture an event associated with some other user (overriding the context-level distinct ID)\n capture('movie joined', distinct_id='some-other-user')\n\n # Capture an event with some properties\n capture('movie played', properties={'movie_id': '123', 'category': 'romcom'})\n\n # Capture an event with some properties\n capture('purchase', properties={'product_id': '123', 'category': 'romcom'})\n # Capture an event with some associated group\n capture('purchase', groups={'company': 'id:5'})\n\n # Adding a tag to the current context will cause it to appear on all subsequent events\n tag_context('some-tag', 'some-value')\n\n capture('another-event') # Will be captured with `'some-tag': 'some-value'` in the properties dict" + }, + { + "id": "example_2", + "name": "Set event properties", + "code": "# Set event properties\nfrom posthog import capture\ncapture(\n \"user_signed_up\",\n distinct_id=\"distinct_id_of_the_user\",\n properties={\n \"login_type\": \"email\",\n \"is_free_trial\": \"true\"\n }\n)" + } + ] + }, + { + "id": "capture_ai", + "title": "capture_ai", + "description": "Capture an AI event on the dedicated AI capture endpoint. Beta: the signature is stable; operational limits (per-event size cap, batching, endpoint) may change without notice. Takes the same arguments and returns the same value as `capture()`: the event UUID, or None when the event was not admitted (disabled client, or dropped by `before_send`). The event is delivered on an isolated queue with its own consumer pool and a higher per-event size cap, posting to the dedicated AI ingestion endpoint. The payload is sent as given \u2014 no redaction or truncation is applied here.", + "details": "", + "category": "Events", + "params": [ + { + "name": "event", + "description": "The event name, normally one of the `$ai_*` event names. **kwargs: Same optional arguments as `capture()`.", + "isOptional": true, + "type": "str" + }, + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalCaptureArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import capture_ai\n\nuuid = capture_ai(\n \"$ai_generation\",\n distinct_id=\"user_123\",\n properties={\"$ai_model\": \"gpt-5\"},\n)" + } + ] + }, + { + "id": "capture_exception", + "title": "capture_exception", + "description": "Capture exceptions that happen in your code.", + "details": "Capture exception is idempotent - if it is called twice with the same exception instance, only a occurrence will be tracked in posthog. This is because, generally, contexts will cause exceptions to be captured automatically. However, to ensure you track an exception, if you catch and do not re-raise it, capturing it manually is recommended, unless you are certain it will have crossed a context boundary (e.g. by existing a `with posthog.new_context():` block already). If the passed exception was raised and caught, the captured stack trace will consist of every frame between where the exception was raised and the point at which it is captured (the \"traceback\"). If the passed exception was never raised, e.g. if you call `posthog.capture_exception(ValueError(\"Some Error\"))`, the stack trace captured will be the full stack trace at the moment the exception was captured. Note that heavy use of contexts will lead to truncated stack traces, as the exception will be captured by the context entered most recently, 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. `capture_exception` takes the same set of optional arguments as `capture`.", + "category": "Events", + "params": [ + { + "name": "exception", + "description": "The exception to capture. If not provided, the current exception is captured via `sys.exc_info()` **kwargs: Optional capture arguments including distinct_id, properties, timestamp, uuid, groups, flags, send_feature_flags, and disable_geoip. Overriding reserved exception properties through ``properties`` is deprecated and will stop working in the next major version.", + "isOptional": false, + "type": "BaseException" + }, + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalCaptureArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Capture exception", + "code": "# Capture exception\nfrom posthog import capture_exception\ntry:\n risky_operation()\nexcept Exception as e:\n capture_exception(e)" + } + ] + }, + { + "id": "evaluate_flags", + "title": "evaluate_flags", + "description": "Evaluate all feature flags for a user in a single call and return a :class:`FeatureFlagEvaluations` snapshot. Branch on ``.is_enabled()`` / ``.get_flag()`` and pass the same snapshot to ``capture()`` via the ``flags`` option so events carry the exact flag values the code branched on. Prefer this over repeated ``get_feature_flag()`` calls and over ``capture(send_feature_flags=True)`` \u2014 it consolidates flag evaluation into a single ``/flags`` request per incoming request.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The user's distinct ID. If ``None``, falls back to the context distinct_id. If still unresolvable, returns an empty snapshot.", + "isOptional": false, + "type": "Number" + }, + { + "name": "groups", + "description": "Mapping of group type to group key.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "Person properties to use for evaluation.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "Group properties keyed by group type.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "If ``True``, never fall back to remote evaluation and omit flags that cannot be evaluated locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup.", + "isOptional": true, + "type": "bool" + }, + { + "name": "flag_keys", + "description": "Optional list that scopes local evaluation, the underlying ``/flags`` request, and the returned snapshot. When omitted or ``None``, all flags are evaluated. An empty list returns an empty snapshot without evaluating flags. A requested key absent from loaded local definitions is included in one remote fallback per ``evaluate_flags`` call unless ``only_evaluate_locally`` is ``True``. If the server also does not know the key, it is omitted from the snapshot.", + "isOptional": true, + "type": "list[str]" + }, + { + "name": "device_id", + "description": "Optional device ID override. If not provided, falls back to the context device_id (which may be set via tracing headers). Used by experience-continuity flags to match users across distinct_id changes.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "FeatureFlagEvaluations" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import evaluate_flags, capture\nflags = evaluate_flags(\"user_123\", person_properties={\"plan\": \"enterprise\"})\nif flags.is_enabled(\"new-dashboard\"):\n render_new_dashboard()\ncapture(\"page_viewed\", distinct_id=\"user_123\", flags=flags)" + } + ] + }, + { + "id": "feature_enabled", + "title": "feature_enabled", + "description": "Use feature flags to enable or disable features for users.", + "details": "You can call `posthog.load_feature_flags()` before to make sure you're not doing unexpected requests.", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "The feature flag key", + "isOptional": true, + "type": "str" + }, + { + "name": "distinct_id", + "description": "The user's distinct ID", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "Groups mapping", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "Person properties", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "Group properties", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to evaluate only locally", + "isOptional": false, + "type": "bool" + }, + { + "name": "send_feature_flag_events", + "description": "Whether to send feature flag events", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "Optional device ID override for experience-continuity flags", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[bool]" + }, + "examples": [ + { + "id": "example_1", + "name": "Boolean feature flag", + "code": "# Boolean feature flag\nfrom posthog import feature_enabled, get_feature_flag_payload\nis_my_flag_enabled = feature_enabled('flag-key', 'distinct_id_of_your_user')\nif is_my_flag_enabled:\n matched_flag_payload = get_feature_flag_payload('flag-key', 'distinct_id_of_your_user')" + } + ] + }, + { + "id": "feature_flag_definitions", + "title": "feature_flag_definitions", + "description": "Returns loaded feature flags.", + "details": "Returns loaded feature flags, if any. Helpful for debugging what flag information you have loaded.", + "category": "Feature flags", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import feature_flag_definitions\ndefinitions = feature_flag_definitions()" + } + ] + }, + { + "id": "flush", + "title": "flush", + "description": "Attempt immediate delivery of queued events and ended spans, keeping the global client reusable. Call ``flush()`` before a short-lived request or serverless invocation returns when using the shared module-level client. Use ``shutdown()`` only for final process cleanup or a client that will not be reused. Flush does not stop the background workers or feature flag poller, and does not end open spans. Returning does not guarantee server receipt: pending work can remain after a timeout or span export failure, and failed events may be dropped by consumers. Calls made directly from SDK callbacks such as ``on_error`` are deferred rather than blocking the worker that invoked the callback.", + "details": "", + "category": "Client management", + "params": [ + { + "name": "timeout_seconds", + "description": "Seconds to wait for event queues to drain, shared across event lanes. Defaults to 10 seconds. Pass ``None`` to wait without a time limit; this does not guarantee successful delivery. Queued spans flush concurrently, waiting up to this many seconds for an in-flight flush before starting a fresh drain budget. A lock wait timeout leaves the in-flight flush to finish. At least one span request is attempted after acquiring the lock, even with no budget left; retriable failures are retried after backoff while budget remains, with one last attempt at the deadline. The last span request is not cut short and is bounded by the client's ``timeout`` in seconds, so this is not a strict wall-clock limit for the whole call. With ``None``, a retriable span failure is left for a later flush rather than retried within this call.", + "isOptional": true, + "type": "float" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "import posthog\nposthog.capture('event_name', distinct_id='user_id')\n# Before a serverless invocation returns; keep the global client open.\nposthog.flush(timeout_seconds=10)" + } + ] + }, + { + "id": "get_active_span", + "title": "get_active_span", + "description": "The span that is active in the current context, or ``None``. Alpha. Only entering a span (``with posthog.start_span(...) as span:``) makes it active; a span started manually is not. Use it to propagate the trace to the next service: ``span.traceparent()`` is the header value.", + "details": "", + "category": "Tracing", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[Span]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "span = posthog.get_active_span()\nif span is not None:\n headers[\"traceparent\"] = span.traceparent()" + } + ] + }, + { + "id": "get_all_flags", + "title": "get_all_flags", + "description": "Get all flags for a given user.", + "details": "Flags are key-value pairs where the key is the flag key and the value is the flag variant, or True, or False.", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The user's distinct ID", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "Groups mapping", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "Person properties", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "Group properties", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to evaluate only locally", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "Optional device ID override for experience-continuity flags", + "isOptional": true, + "type": "str" + }, + { + "name": "flag_keys_to_evaluate", + "description": "Optional list of flag keys to evaluate (evaluates all if None)", + "isOptional": true, + "type": "list[str]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[dict[str, Union[bool, str]]]" + }, + "examples": [ + { + "id": "example_1", + "name": "All flags for user", + "code": "# All flags for user\nfrom posthog import get_all_flags\nget_all_flags('distinct_id_of_your_user')" + } + ] + }, + { + "id": "get_all_flags_and_payloads", + "title": "get_all_flags_and_payloads", + "description": "Get all feature flag values and payloads for a user.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The user's distinct ID.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "Mapping of group type to group key.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "Person properties to use for evaluation.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "Group properties keyed by group type.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to evaluate only locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup.", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "Optional device ID override for experience-continuity flags.", + "isOptional": true, + "type": "str" + }, + { + "name": "flag_keys_to_evaluate", + "description": "Optional list of flag keys to evaluate. Evaluates all flags when omitted.", + "isOptional": true, + "type": "list[str]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "FlagsAndPayloads" + } + }, + { + "id": "get_feature_flag", + "title": "get_feature_flag", + "description": "Get feature flag variant for users. Used with experiments.", + "details": "`groups` are a mapping from group type to group key. So, if you have a group type of \"organization\" and a group key of \"5\", you would pass groups={\"organization\": \"5\"}. `group_properties` take the format: { group_type_name: { group_properties } }. So, for example, if you have the group type \"organization\" and the group key \"5\", with the properties name, and employee count, you'll send these as: group_properties={\"organization\": {\"name\": \"PostHog\", \"employees\": 11}}.", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "The feature flag key", + "isOptional": true, + "type": "str" + }, + { + "name": "distinct_id", + "description": "The user's distinct ID", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "Groups mapping from group type to group key", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "Person properties", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "Group properties in format { group_type_name: { group_properties } }", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to evaluate only locally", + "isOptional": false, + "type": "bool" + }, + { + "name": "send_feature_flag_events", + "description": "Whether to send feature flag events", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "Optional device ID override for experience-continuity flags", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Union[bool, str, any]" + }, + "examples": [ + { + "id": "example_1", + "name": "Multivariate feature flag", + "code": "# Multivariate feature flag\nfrom posthog import get_feature_flag, get_feature_flag_payload\nenabled_variant = get_feature_flag('flag-key', 'distinct_id_of_your_user')\nif enabled_variant == 'variant-key':\n matched_flag_payload = get_feature_flag_payload('flag-key', 'distinct_id_of_your_user')" + } + ] + }, + { + "id": "get_feature_flag_evaluation_runtime", + "title": "get_feature_flag_evaluation_runtime", + "description": "Return where a locally loaded feature flag is meant to be evaluated.", + "details": "Reads the `evaluation_runtime` each flag definition carries, so no extra request is made. Returns `None` when local evaluation has not loaded a definition for this key. A definition that carries no runtime reports `FeatureFlagEvaluationRuntime.ALL`, the default PostHog applies.", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[FeatureFlagEvaluationRuntime]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import FeatureFlagEvaluationRuntime, get_feature_flag_evaluation_runtime\nruntime = get_feature_flag_evaluation_runtime(\"my-flag\")" + } + ] + }, + { + "id": "get_feature_flag_keys_by_evaluation_runtime", + "title": "get_feature_flag_keys_by_evaluation_runtime", + "description": "Return the keys of locally loaded flags that a runtime can evaluate.", + "details": "A flag set to `FeatureFlagEvaluationRuntime.ALL` suits either runtime, so it is returned for `CLIENT` and for `SERVER`. Use this to decide which flags to hand to a browser when a backend serves flags to its own frontend.", + "category": "Feature flags", + "params": [ + { + "name": "evaluation_runtime", + "description": "", + "isOptional": true, + "type": "FeatureFlagEvaluationRuntime" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "list[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import FeatureFlagEvaluationRuntime, get_feature_flag_keys_by_evaluation_runtime\nclient_keys = get_feature_flag_keys_by_evaluation_runtime(FeatureFlagEvaluationRuntime.CLIENT)" + } + ] + }, + { + "id": "get_feature_flag_payload", + "title": "get_feature_flag_payload", + "description": "Get the payload associated with a feature flag value. Deprecated for new code. Prefer ``evaluate_flags()`` and ``flags.get_flag_payload(key)`` so flag evaluation happens once per request.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "The feature flag key.", + "isOptional": true, + "type": "str" + }, + { + "name": "distinct_id", + "description": "The user's distinct ID.", + "isOptional": true, + "type": "Number" + }, + { + "name": "match_value", + "description": "Optional flag value to use when selecting a payload.", + "isOptional": false, + "type": "bool" + }, + { + "name": "groups", + "description": "Mapping of group type to group key.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "Person properties to use for evaluation.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "Group properties keyed by group type.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to evaluate only locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "send_feature_flag_events", + "description": "Whether to send a $feature_flag_called event.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup.", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "Optional device ID override for experience-continuity flags.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[object]" + } + }, + { + "id": "get_feature_flag_result", + "title": "get_feature_flag_result", + "description": "Get a FeatureFlagResult object which contains the flag result and payload. This method evaluates a feature flag and returns a FeatureFlagResult object containing: - enabled: Whether the flag is enabled - variant: The variant value if the flag has variants - payload: The payload associated with the flag (automatically deserialized from JSON) - key: The flag key - reason: Why the flag was enabled/disabled", + "details": "", + "category": null, + "params": [ + { + "name": "key", + "description": "The feature flag key.", + "isOptional": true, + "type": "str" + }, + { + "name": "distinct_id", + "description": "The user's distinct ID.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "Mapping of group type to group key.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "Person properties to use for evaluation.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "Group properties keyed by group type.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to evaluate only locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "send_feature_flag_events", + "description": "Whether to send a $feature_flag_called event.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup.", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "Optional device ID override for experience-continuity flags.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[FeatureFlagResult]" + } + }, + { + "id": "get_remote_config_payload", + "title": "get_remote_config_payload", + "description": "Get the payload for a remote config feature flag.", + "details": "", + "category": null, + "params": [ + { + "name": "key", + "description": "The key of the feature flag", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "get_tags", + "title": "get_tags", + "description": "Get all tags from the current context. Returns: Dict of all tags in the current context", + "details": "", + "category": "Contexts", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "dict[str, Any]" + } + }, + { + "id": "group_identify", + "title": "group_identify", + "description": "Set properties on a group.", + "details": "", + "category": "Identification", + "params": [ + { + "name": "group_type", + "description": "Type of your group. Required - the call is dropped with a warning if it is missing or empty.", + "isOptional": true, + "type": "str" + }, + { + "name": "group_key", + "description": "Unique identifier of the group. Required - the call is dropped with a warning if it is missing or empty.", + "isOptional": true, + "type": "str" + }, + { + "name": "properties", + "description": "Properties to set on the group", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "timestamp", + "description": "Optional timestamp for the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC.", + "isOptional": false, + "type": "datetime" + }, + { + "name": "uuid", + "description": "Optional UUID for the event", + "isOptional": true, + "type": "str" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup", + "isOptional": true, + "type": "bool" + }, + { + "name": "distinct_id", + "description": "Optional distinct ID of the user performing the action", + "isOptional": false, + "type": "Number" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Group identify", + "code": "# Group identify\nfrom posthog import group_identify\ngroup_identify('company', 'company_id_in_your_db', {\n 'name': 'Awesome Inc.',\n 'employees': 11\n})" + } + ] + }, + { + "id": "identify_context", + "title": "identify_context", + "description": "Identify the current context with a distinct ID.", + "details": "", + "category": "Identification", + "params": [ + { + "name": "distinct_id", + "description": "The distinct ID to associate with the current context and its children", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import identify_context\nidentify_context(\"user_123\")" + } + ] + }, + { + "id": "join", + "title": "join", + "description": "Attempt to process queued events and stop the client's background workers. Use `shutdown()` directly in most cases. Failed or undrainable events may be dropped and reported through logging or ``on_error``; returning does not guarantee server receipt. Lifecycle cleanup is attempted once, and cleanup failures are logged without retry.", + "details": "", + "category": "Client management", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import join\njoin()" + } + ] + }, + { + "id": "load_feature_flags", + "title": "load_feature_flags", + "description": "Load feature flag definitions from PostHog.", + "details": "", + "category": "Feature flags", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import load_feature_flags\nload_feature_flags()" + } + ] + }, + { + "id": "new_context", + "title": "new_context", + "description": "Create a new context scope that will be active for the duration of the with block.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "fresh", + "description": "Whether to start with a fresh context (default: False)", + "isOptional": false, + "type": "bool" + }, + { + "name": "capture_exceptions", + "description": "Whether to capture exceptions raised within the context. If omitted, defaults to the relevant client's exception autocapture setting.", + "isOptional": true, + "type": "bool" + }, + { + "name": "client", + "description": "Optional Posthog client instance to use for this context (default: None)", + "isOptional": true, + "type": "Client" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import new_context, tag, capture\nwith new_context():\n tag(\"request_id\", \"123\")\n capture(\"event_name\", properties={\"property\": \"value\"})" + } + ] + }, + { + "id": "scoped", + "title": "scoped", + "description": "Decorator that creates a new context for the function.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "fresh", + "description": "Whether to start with a fresh context (default: False)", + "isOptional": false, + "type": "bool" + }, + { + "name": "capture_exceptions", + "description": "Whether to capture and track exceptions with posthog error tracking. If omitted, defaults to the global exception autocapture setting.", + "isOptional": true, + "type": "bool" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import scoped, tag, capture\n@scoped()\ndef process_payment(payment_id):\n tag(\"payment_id\", payment_id)\n capture(\"payment_started\")" + } + ] + }, + { + "id": "set", + "title": "set", + "description": "Set properties on a user record.", + "details": "This will overwrite previous people property values. Generally operates similar to `capture`, with distinct_id being an optional argument, defaulting to the current context's distinct ID. If there is no context-level distinct ID, and no override distinct_id is passed, this function will do nothing. Context tags are folded into $set properties, so tagging the current context and then calling `set` will cause those tags to be set on the user (unlike capture, which causes them to just be set on the event).", + "category": "Identification", + "params": [ + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalSetArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Set person properties", + "code": "# Set person properties\nfrom posthog import set\nset(distinct_id='distinct_id', properties={'name': 'Max Hedgehog'})" + } + ] + }, + { + "id": "set_capture_exception_code_variables_context", + "title": "set_capture_exception_code_variables_context", + "description": "Override code-variable capture for exceptions in the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "enabled", + "description": "Whether exceptions captured in this context should include local variable values from stack frames.", + "isOptional": true, + "type": "bool" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "set_code_variables_detect_secrets_context", + "title": "set_code_variables_detect_secrets_context", + "description": "Whether to apply entropy-based secret detection as a last-resort redaction of high-entropy values (API keys, tokens, strong passwords) in captured code variables for the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "enabled", + "description": "", + "isOptional": true, + "type": "bool" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "set_code_variables_ignore_patterns_context", + "title": "set_code_variables_ignore_patterns_context", + "description": "Override code-variable ignore patterns for exceptions in the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "ignore_patterns", + "description": "Variable-name patterns that should be omitted entirely when code variables are captured.", + "isOptional": true, + "type": "list[str]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "set_code_variables_mask_patterns_context", + "title": "set_code_variables_mask_patterns_context", + "description": "Override code-variable mask patterns for exceptions in the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "mask_patterns", + "description": "Variable-name patterns whose values should be replaced with ``***`` when code variables are captured.", + "isOptional": true, + "type": "list[str]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "set_code_variables_mask_url_credentials_context", + "title": "set_code_variables_mask_url_credentials_context", + "description": "Whether to scrub credentials embedded in URLs/DSNs (e.g. user:pass@host) from captured code variables for the current context.", + "details": "", + "category": null, + "params": [ + { + "name": "enabled", + "description": "", + "isOptional": true, + "type": "bool" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "set_context_device_id", + "title": "set_context_device_id", + "description": "Set the device ID for the current context, associating all feature flag requests in this or child contexts with the given device ID.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "device_id", + "description": "The device ID to associate with the current context and its children", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import set_context_device_id\nset_context_device_id(\"device_123\")" + } + ] + }, + { + "id": "set_context_session", + "title": "set_context_session", + "description": "Set the session ID for the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "session_id", + "description": "The session ID to associate with the current context and its children", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import set_context_session\nset_context_session(\"session_123\")" + } + ] + }, + { + "id": "set_once", + "title": "set_once", + "description": "Set properties on a user record, only if they do not yet exist.", + "details": "This will not overwrite previous people property values, unlike `set`. Otherwise, operates in an identical manner to `set`.", + "category": "Identification", + "params": [ + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalSetArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Set property once", + "code": "# Set property once\nfrom posthog import set_once\nset_once(distinct_id='distinct_id', properties={'initial_url': '/blog'})" + } + ] + }, + { + "id": "setup", + "title": "setup", + "description": "Create or return the global PostHog client configured by module settings. Most applications should either instantiate ``Posthog`` directly or set ``posthog.api_key``/other module settings before calling top-level helpers. ``setup()`` is called automatically by global APIs such as ``capture()``. Returns: The global ``Client`` instance. If both ``api_key`` and ``project_api_key`` are missing or blank, the client is disabled and module-level calls become no-ops.", + "details": "", + "category": "Initialization", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Client" + } + }, + { + "id": "shutdown", + "title": "shutdown", + "description": "Attempt final delivery and permanently shut down the global client. Call this for final process cleanup, not at the end of each request or serverless invocation that shares the module-level client. Use ``flush()`` before returning from those invocations instead. Shutdown closes capture lanes, stops background workers and the feature flag poller, and tears down integrations. The global client is not recreated automatically after shutdown. This normally blocks until queued events have been attempted and cleanup finishes. Failed or undrainable events may be dropped and reported through logging or ``on_error``; returning does not guarantee server receipt. Event draining has no timeout. Queued spans get a final flush with up to 30 seconds waiting for an in-flight flush, then a fresh 30-second drain budget; the last HTTP request is not cut short. Unsent queued spans and still-open spans are discarded with a warning. Lifecycle cleanup is attempted once, and cleanup failures are logged without retry. Calls made directly from SDK callbacks such as ``on_error`` are deferred to avoid deadlocking the worker. If blocking completion is required, signal an application-owned thread, return from the callback, and call ``shutdown()`` from that thread. Do not wait inside a callback for another thread or task calling a lifecycle method.", + "details": "", + "category": "Client management", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import shutdown\nshutdown()" + } + ] + }, + { + "id": "start_span", + "title": "start_span", + "description": "Start a span for distributed tracing. Alpha. Returns a span handle. Use it as a context manager to make it the active span for the block and end it on exit (recording a raised exception on the way out); or call ``end()`` yourself for a span that cannot wrap a block. Spans started inside the block nest under it automatically. Always returns a usable handle, even when tracing is off, so calling code never branches.", + "details": "", + "category": "Tracing", + "params": [ + { + "name": "name", + "description": "A low-cardinality operation name, e.g. ``GET /users/:id``. Variable values belong in attributes, not the name.", + "isOptional": true, + "type": "str" + }, + { + "name": "kind", + "description": "``internal`` (default), ``server``, ``client``, ``producer`` or ``consumer``.", + "isOptional": true, + "type": "str" + }, + { + "name": "attributes", + "description": "Initial attributes.", + "isOptional": true, + "type": "Mapping[str, Any]" + }, + { + "name": "parent", + "description": "A span handle, or an inbound W3C ``traceparent`` header value to continue a remote trace. Defaults to the active span.", + "isOptional": false, + "type": "Span" + }, + { + "name": "tracestate", + "description": "The inbound ``tracestate`` header accompanying a ``traceparent`` string ``parent``; preserved and propagated.", + "isOptional": true, + "type": "str" + }, + { + "name": "start_time", + "description": "A ``datetime`` or epoch seconds, to backdate the span.", + "isOptional": false, + "type": "datetime" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Span" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "import posthog\nposthog.traces = {\"service_name\": \"checkout-api\"}\n\nwith posthog.start_span(\"POST /checkout\", parent=request.headers.get(\"traceparent\")) as span:\n span.set_attribute(\"plan\", user.plan)\n with posthog.start_span(\"db.query\", kind=\"client\"):\n ...\n outgoing_headers = {\"traceparent\": span.traceparent()}" + } + ] + }, + { + "id": "tag", + "title": "tag", + "description": "Add a tag to the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "name", + "description": "The tag key", + "isOptional": true, + "type": "str" + }, + { + "name": "value", + "description": "The tag value", + "isOptional": true, + "type": "Any" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import tag\ntag(\"user_id\", \"123\")" + } + ] + } + ] + } + ], + "categories": [ + "Initialization", + "Identification", + "Capture", + "Error Tracking", + "Feature flags", + "Tracing", + "Contexts", + "Events", + "Client management" + ] +} \ No newline at end of file diff --git a/references/posthog-python-references-latest.json b/references/posthog-python-references-latest.json index 3ced9019..226ca01c 100644 --- a/references/posthog-python-references-latest.json +++ b/references/posthog-python-references-latest.json @@ -2,7 +2,7 @@ "id": "posthog-python", "hogRef": "0.3", "info": { - "version": "7.75.9", + "version": "7.75.10", "id": "posthog-python", "title": "PostHog Python SDK", "description": "Integrate PostHog into any python application.", From 153128ef193aac775acbf64ad8e3315298fc2e83 Mon Sep 17 00:00:00 2001 From: Carlos Marchal Date: Fri, 23 Oct 2026 17:22:17 +0200 Subject: [PATCH 16/60] fix(aio): keep content out of ai events in privacy mode (#1105) * fix: ai privacy mode leaks * fix: address privacy mode review findings * fix: strip remaining otel content in privacy mode * fix: redact mcp credentials in otel tool definitions * fix: address otel privacy review findings * fix: always redact mcp credentials in otel tool definitions * fix: strip otel embedding vectors in privacy mode * fix: drop only the otel span that fails redaction * fix: keep vercel rerank scores in otel privacy mode * fix: strip logfire payload attributes in otel privacy mode (cherry picked from commit 04e6a4c1c4f15e4dd0d010dee5fa83244f9b6dd8) --- .sampo/changesets/ai-privacy-mode-leaks.md | 12 + posthog/ai/anthropic/anthropic_converter.py | 4 +- posthog/ai/gemini/_shared.py | 12 +- posthog/ai/gemini/gemini.py | 6 +- posthog/ai/gemini/gemini_async.py | 6 +- posthog/ai/gemini/gemini_converter.py | 4 +- posthog/ai/langchain/callbacks.py | 69 +- posthog/ai/langchain/middleware.py | 12 +- posthog/ai/openai/openai_converter.py | 8 +- posthog/ai/openai_agents/processor.py | 36 +- posthog/ai/otel/_privacy.py | 344 ++++++++ posthog/ai/otel/exporter.py | 17 +- posthog/ai/otel/processor.py | 18 +- posthog/ai/utils.py | 234 +++++- posthog/test/ai/gemini/test_gemini_parity.py | 20 +- posthog/test/ai/langchain/test_callbacks.py | 22 +- posthog/test/ai/langchain/test_middleware.py | 2 +- posthog/test/ai/test_privacy_mode.py | 800 +++++++++++++++++++ references/public_api_snapshot.txt | 6 +- 19 files changed, 1548 insertions(+), 84 deletions(-) create mode 100644 .sampo/changesets/ai-privacy-mode-leaks.md create mode 100644 posthog/ai/otel/_privacy.py create mode 100644 posthog/test/ai/test_privacy_mode.py diff --git a/.sampo/changesets/ai-privacy-mode-leaks.md b/.sampo/changesets/ai-privacy-mode-leaks.md new file mode 100644 index 00000000..8eb4ecec --- /dev/null +++ b/.sampo/changesets/ai-privacy-mode-leaks.md @@ -0,0 +1,12 @@ +--- +pypi/posthog: patch +--- + +Privacy mode now covers error text and the remaining content paths in the AI integrations. + +- `$ai_error` no longer carries the exception message when privacy mode is on. Provider errors and validation errors often quote the prompt or the model output, so the SDK now sends a summary built only from structured fields: the class name, HTTP status, provider error code, `param`, request ID and the class names of chained causes (for example `BadRequestError: 400 invalid_request_error param=messages[3].content request_id=req_123`). This applies to the OpenAI, Anthropic and Gemini wrappers (including Gemini embeddings), LangChain generations, spans and traces, and OpenAI Agents spans. +- The LangChain callback handler no longer sends an `$exception` event for a failed run when privacy mode is on. +- `$ai_tools` masks the `headers` and `authorization` of MCP tool definitions (`type: "mcp"`) with `[REDACTED]`, with or without privacy mode. The tools you pass in are not modified. +- Gemini: a per-call `posthog_privacy_mode=False` no longer turns off privacy mode set on the client, matching the OpenAI and Anthropic wrappers. +- OpenAI Agents: privacy mode now redacts `$ai_trace_metadata` and the `prompt` and `instructions` in transcription and speech span `model_config`. +- OpenTelemetry: `PostHogSpanProcessor` and `PostHogTraceExporter` accept `privacy_mode` and `client`, and also honor the client's (or the module-level) `privacy_mode`. When on, exported spans drop prompt, completion, tool argument and tool result attributes, non-exception span events, exception messages and stack traces, and the span status description. diff --git a/posthog/ai/anthropic/anthropic_converter.py b/posthog/ai/anthropic/anthropic_converter.py index 650c5587..1e476c5c 100644 --- a/posthog/ai/anthropic/anthropic_converter.py +++ b/posthog/ai/anthropic/anthropic_converter.py @@ -18,7 +18,7 @@ TokenUsage, ToolInProgress, ) -from posthog.ai.utils import serialize_raw_usage +from posthog.ai.utils import _redact_mcp_tool_credentials, serialize_raw_usage def format_anthropic_response(response: Any) -> List[FormattedMessage]: @@ -153,7 +153,7 @@ def extract_anthropic_tools(kwargs: Dict[str, Any]) -> Optional[Any]: Tool definitions if present, None otherwise """ - return kwargs.get("tools", None) + return _redact_mcp_tool_credentials(kwargs.get("tools", None)) def format_anthropic_streaming_content( diff --git a/posthog/ai/gemini/_shared.py b/posthog/ai/gemini/_shared.py index 9c8e7c8e..9096b859 100644 --- a/posthog/ai/gemini/_shared.py +++ b/posthog/ai/gemini/_shared.py @@ -16,6 +16,7 @@ _resolve_ai_client, capture_streaming_event, finalize_ai_content, + _format_ai_error, _get_http_status, merge_system_prompt, with_privacy_mode, @@ -167,11 +168,8 @@ def _merge_posthog_params( if call_distinct_id is not None else self._default_distinct_id ) - privacy_mode = ( - call_privacy_mode - if call_privacy_mode is not None - else self._default_privacy_mode - ) + # A call can add redaction but never lift the client default. + privacy_mode = self._default_privacy_mode or call_privacy_mode is True groups = call_groups if call_groups is not None else self._default_groups properties = dict(self._default_properties) @@ -256,7 +254,9 @@ def _capture_embedding_outcome( if error: event_properties["$ai_is_error"] = True - event_properties["$ai_error"] = str(error) + event_properties["$ai_error"] = _format_ai_error( + self._ph_client, privacy_mode, error + ) _capture_ai_event( self._ph_client, diff --git a/posthog/ai/gemini/gemini.py b/posthog/ai/gemini/gemini.py index 71224af9..3d7659a0 100644 --- a/posthog/ai/gemini/gemini.py +++ b/posthog/ai/gemini/gemini.py @@ -87,7 +87,7 @@ def __init__( posthog_client: PostHog client for tracking usage posthog_distinct_id: Default distinct ID for all calls (can be overridden per call) posthog_properties: Default properties for all calls (can be overridden per call) - posthog_privacy_mode: Default privacy mode for all calls (can be overridden per call) + posthog_privacy_mode: Default privacy mode for all calls (a call can enable it, not disable it) posthog_groups: Default groups for all calls (can be overridden per call) **kwargs: Additional arguments (for future compatibility) """ @@ -212,7 +212,7 @@ def generate_content( posthog_distinct_id: ID to associate with the usage event (overrides client default) posthog_trace_id: Trace UUID for linking events (auto-generated if not provided) posthog_properties: Extra properties to include in the event (merged with client defaults) - posthog_privacy_mode: Whether to redact sensitive information (overrides client default) + posthog_privacy_mode: Whether to redact sensitive information (cannot disable a client default of True) posthog_groups: Group analytics properties (overrides client default) **kwargs: Arguments passed to Gemini's generate_content """ @@ -384,7 +384,7 @@ def embed_content( posthog_distinct_id: ID to associate with the usage event (overrides client default) posthog_trace_id: Trace UUID for linking events (auto-generated if not provided) posthog_properties: Extra properties to include in the event (merged with client defaults) - posthog_privacy_mode: Whether to redact sensitive information (overrides client default) + posthog_privacy_mode: Whether to redact sensitive information (cannot disable a client default of True) posthog_groups: Group analytics properties (overrides client default) **kwargs: Arguments passed to Gemini's embed_content (e.g., config) """ diff --git a/posthog/ai/gemini/gemini_async.py b/posthog/ai/gemini/gemini_async.py index 6156faf4..d828bacc 100644 --- a/posthog/ai/gemini/gemini_async.py +++ b/posthog/ai/gemini/gemini_async.py @@ -82,7 +82,7 @@ def __init__( posthog_client: PostHog client for tracking usage posthog_distinct_id: Default distinct ID for all calls (can be overridden per call) posthog_properties: Default properties for all calls (can be overridden per call) - posthog_privacy_mode: Default privacy mode for all calls (can be overridden per call) + posthog_privacy_mode: Default privacy mode for all calls (a call can enable it, not disable it) posthog_groups: Default groups for all calls (can be overridden per call) **kwargs: Additional arguments (for future compatibility) """ @@ -196,7 +196,7 @@ async def generate_content( posthog_distinct_id: ID to associate with the usage event (overrides client default) posthog_trace_id: Trace UUID for linking events (auto-generated if not provided) posthog_properties: Extra properties to include in the event (merged with client defaults) - posthog_privacy_mode: Whether to redact sensitive information (overrides client default) + posthog_privacy_mode: Whether to redact sensitive information (cannot disable a client default of True) posthog_groups: Group analytics properties (overrides client default) **kwargs: Arguments passed to Gemini's generate_content """ @@ -371,7 +371,7 @@ async def embed_content( posthog_distinct_id: ID to associate with the usage event (overrides client default) posthog_trace_id: Trace UUID for linking events (auto-generated if not provided) posthog_properties: Extra properties to include in the event (merged with client defaults) - posthog_privacy_mode: Whether to redact sensitive information (overrides client default) + posthog_privacy_mode: Whether to redact sensitive information (cannot disable a client default of True) posthog_groups: Group analytics properties (overrides client default) **kwargs: Arguments passed to Gemini's embed_content (e.g., config) """ diff --git a/posthog/ai/gemini/gemini_converter.py b/posthog/ai/gemini/gemini_converter.py index 669f6329..ae452274 100644 --- a/posthog/ai/gemini/gemini_converter.py +++ b/posthog/ai/gemini/gemini_converter.py @@ -13,7 +13,7 @@ FormattedMessage, TokenUsage, ) -from posthog.ai.utils import serialize_raw_usage +from posthog.ai.utils import _redact_mcp_tool_credentials, serialize_raw_usage _MEDIA_KINDS = {"image": "image", "video": "video", "audio": "audio"} _BARE_PART_KEYS = { @@ -364,7 +364,7 @@ def extract_gemini_tools(kwargs: Dict[str, Any]) -> Optional[Any]: """ if "config" in kwargs and hasattr(kwargs["config"], "tools"): - return kwargs["config"].tools + return _redact_mcp_tool_credentials(kwargs["config"].tools) return None diff --git a/posthog/ai/langchain/callbacks.py b/posthog/ai/langchain/callbacks.py index da46718f..6a50f67f 100644 --- a/posthog/ai/langchain/callbacks.py +++ b/posthog/ai/langchain/callbacks.py @@ -47,7 +47,10 @@ _extract_cache_creation_ttl_breakdown, _resolve_ai_client, finalize_ai_content, + _format_ai_error, get_model_params, + _privacy_mode_enabled, + _redact_mcp_tool_credentials, _responses_stop_reason, with_privacy_mode, ) @@ -560,14 +563,12 @@ def _capture_trace_or_span( event_properties.update(self._properties) if isinstance(outputs, BaseException): - event_properties["$ai_error"] = _stringify_exception(outputs) + event_properties["$ai_error"] = _format_ai_error( + self._ph_client, self._privacy_mode, outputs, _stringify_exception + ) event_properties["$ai_is_error"] = True - event_properties = _capture_exception_and_update_properties( - self._ph_client, - outputs, - self._distinct_id, - self._groups, - event_properties, + event_properties = self._capture_exception_and_update_properties( + outputs, event_properties ) elif outputs is not None: @@ -586,6 +587,25 @@ def _capture_trace_or_span( personless=self._distinct_id is None, ) + def _capture_exception_and_update_properties( + self, exception: BaseException, event_properties: Dict[str, Any] + ) -> Dict[str, Any]: + # An `$exception` event carries the message and stack, which echo prompts and output. + if not self._ph_client.enable_exception_autocapture or _privacy_mode_enabled( + self._ph_client, self._privacy_mode + ): + return event_properties + + exception_id = self._ph_client.capture_exception( + exception, + distinct_id=self._distinct_id, + groups=self._groups, + properties=event_properties, + ) + if exception_id: + event_properties["$exception_event_id"] = exception_id + return event_properties + def _pop_run_and_capture_generation( self, run_id: UUID, @@ -655,22 +675,20 @@ def _capture_generation( event_properties.update(run.posthog_properties) if run.tools: - event_properties["$ai_tools"] = run.tools + event_properties["$ai_tools"] = _redact_mcp_tool_credentials(run.tools) if self._properties: event_properties.update(self._properties) if isinstance(output, BaseException): event_properties["$ai_http_status"] = _get_http_status(output) - event_properties["$ai_error"] = _stringify_exception(output) + event_properties["$ai_error"] = _format_ai_error( + self._ph_client, self._privacy_mode, output, _stringify_exception + ) event_properties["$ai_is_error"] = True - event_properties = _capture_exception_and_update_properties( - self._ph_client, - output, - self._distinct_id, - self._groups, - event_properties, + event_properties = self._capture_exception_and_update_properties( + output, event_properties ) else: # Add usage @@ -1007,27 +1025,6 @@ def _parse_usage( return llm_usage -def _capture_exception_and_update_properties( - client: Client, - exception: BaseException, - distinct_id: Optional[Union[str, int, UUID]], - groups: Optional[Dict[str, Any]], - event_properties: Dict[str, Any], -): - if client.enable_exception_autocapture: - exception_id = client.capture_exception( - exception, - distinct_id=distinct_id, - groups=groups, - properties=event_properties, - ) - - if exception_id: - event_properties["$exception_event_id"] = exception_id - - return event_properties - - def _get_http_status(error: BaseException) -> int: # OpenAI: https://github.com/openai/openai-python/blob/main/src/openai/_exceptions.py # Anthropic: https://github.com/anthropics/anthropic-sdk-python/blob/main/src/anthropic/_exceptions.py diff --git a/posthog/ai/langchain/middleware.py b/posthog/ai/langchain/middleware.py index 34783747..8cf282a8 100644 --- a/posthog/ai/langchain/middleware.py +++ b/posthog/ai/langchain/middleware.py @@ -21,6 +21,7 @@ from langgraph.errors import GraphBubbleUp from langgraph.types import Command from ...client import Client +from ..utils import _with_privacy_safe_message from .callbacks import ( CallbackHandler, GenerationMetadata, @@ -329,13 +330,10 @@ def _privacy_mode_enabled(self) -> bool: or getattr(self._callback._ph_client, "privacy_mode", False) ) - def _returned_tool_error(self, response: ToolMessage) -> RuntimeError: - message = ( - "Tool returned an error" - if self._privacy_mode_enabled() - else str(response.content) - ) - return RuntimeError(message) + def _returned_tool_error(self, response: ToolMessage) -> BaseException: + if self._privacy_mode_enabled(): + return _with_privacy_safe_message(RuntimeError("Tool returned an error")) + return RuntimeError(str(response.content)) @staticmethod def _normalize_tool(tool: Any) -> dict[str, Any]: diff --git a/posthog/ai/openai/openai_converter.py b/posthog/ai/openai/openai_converter.py index 78487d19..6faf1a6e 100644 --- a/posthog/ai/openai/openai_converter.py +++ b/posthog/ai/openai/openai_converter.py @@ -17,7 +17,11 @@ FormattedTextContent, TokenUsage, ) -from posthog.ai.utils import _responses_stop_reason, serialize_raw_usage +from posthog.ai.utils import ( + _responses_stop_reason, + _redact_mcp_tool_credentials, + serialize_raw_usage, +) def _item_attr(item: Any, name: str, default: Any = None) -> Any: @@ -299,7 +303,7 @@ def extract_openai_tools(kwargs: Dict[str, Any]) -> Optional[Any]: # Check for tools parameter (newer API) if "tools" in kwargs: - return kwargs["tools"] + return _redact_mcp_tool_credentials(kwargs["tools"]) # Check for functions parameter (older API) if "functions" in kwargs: diff --git a/posthog/ai/openai_agents/processor.py b/posthog/ai/openai_agents/processor.py index f54c43e5..db7ab15d 100644 --- a/posthog/ai/openai_agents/processor.py +++ b/posthog/ai/openai_agents/processor.py @@ -25,12 +25,16 @@ _capture_processor_event, _resolve_ai_client, finalize_ai_content, + _format_error_summary, + _privacy_mode_enabled, with_privacy_mode as _with_privacy_mode, ) from posthog.client import Client log = logging.getLogger("posthog") +_AUDIO_MODEL_CONFIG_CONTENT_KEYS = frozenset({"prompt", "instructions"}) + def _parse_iso_timestamp(iso_str: Optional[str]) -> Optional[float]: """Parse ISO timestamp to Unix timestamp.""" @@ -125,6 +129,15 @@ def _with_privacy_mode(self, value: Any) -> Any: """Apply privacy mode redaction if enabled.""" return _with_privacy_mode(self._client, self._privacy_mode, value) + def _privacy_enabled(self) -> bool: + return _privacy_mode_enabled(self._client, self._privacy_mode) + + @staticmethod + def _summarize_span_error(error_info: Any) -> str: + # Agents SDK span errors are plain dicts, not exceptions: only a type name is structured. + error_type = error_info.get("type") if isinstance(error_info, dict) else None + return _format_error_summary(error_type) + def _evict_stale_entries(self) -> None: """Evict oldest entries if dicts exceed max size to prevent unbounded growth.""" if len(self._span_start_times) > self._max_tracked_entries: @@ -230,7 +243,9 @@ def on_trace_end(self, trace: Trace) -> None: # Include trace metadata if present if metadata: - properties["$ai_trace_metadata"] = _ensure_serializable(metadata) + properties["$ai_trace_metadata"] = self._with_privacy_mode( + _ensure_serializable(metadata) + ) self._capture_event( event="$ai_trace", @@ -314,7 +329,11 @@ def on_span_end(self, span: Span[Any]) -> None: error_properties = { "$ai_is_error": True, - "$ai_error": error_message, + "$ai_error": ( + self._summarize_span_error(error_info) + if self._privacy_enabled() + else error_message + ), "$ai_error_type": error_type, } @@ -774,7 +793,9 @@ def _handle_audio_span( # Add model config if available (pass-through property) if hasattr(span_data, "model_config") and span_data.model_config: - properties["model_config"] = _ensure_serializable(span_data.model_config) + properties["model_config"] = self._redact_audio_model_config( + _ensure_serializable(span_data.model_config) + ) # Add time to first audio byte for speech spans (pass-through property) if hasattr(span_data, "first_content_at") and span_data.first_content_at: @@ -821,6 +842,15 @@ def _handle_audio_span( self._capture_event("$ai_span", properties, distinct_id) + def _redact_audio_model_config(self, model_config: Any) -> Any: + # The STT prompt and TTS instructions are user-authored text. + if not isinstance(model_config, dict) or not self._privacy_enabled(): + return model_config + return { + key: (None if key in _AUDIO_MODEL_CONFIG_CONTENT_KEYS else value) + for key, value in model_config.items() + } + def _handle_mcp_span( self, span_data: MCPListToolsSpanData, diff --git a/posthog/ai/otel/_privacy.py b/posthog/ai/otel/_privacy.py new file mode 100644 index 00000000..5a2114ee --- /dev/null +++ b/posthog/ai/otel/_privacy.py @@ -0,0 +1,344 @@ +"""Privacy mode for OpenTelemetry spans exported to PostHog.""" + +import json +import logging +import re +from typing import TYPE_CHECKING, Any, Dict, List, Optional, Sequence + +from opentelemetry.attributes import BoundedAttributes +from opentelemetry.sdk.trace import Event, ReadableSpan +from opentelemetry.sdk.util import BoundedList +from opentelemetry.trace.status import Status +from opentelemetry.util.types import Attributes, AttributeValue + +from ..utils import _redact_mcp_tool, _redact_mcp_tool_credentials + +if TYPE_CHECKING: + from ...client import Client + +log = logging.getLogger("posthog") + +# Attributes that carry prompts, outputs, tool calls, retrieved documents or error text. +# Ingestion keeps unmapped attributes as event properties, so this also covers attributes +# it does not map. Tool definitions ($ai_tools) are not content and stay, as in the SDK +# wrappers. +_CONTENT_ATTRIBUTES = frozenset( + { + # Vercel AI SDK + "ai.prompt", + "ai.prompt.messages", + "ai.request.text", + "ai.documents", + "ai.response.text", + "ai.response.object", + "ai.response.toolCalls", + "ai.response.reasoning", + "ai.response.reasoningDetails", + "ai.response.files", + "ai.toolCall.args", + "ai.toolCall.result", + "ai.decision.state", + "ai.decision.questions", + "ai.decision.answers", + "ai.value", + "ai.values", + "ai.embedding", + "ai.embeddings", + # OpenTelemetry GenAI semantic conventions + "gen_ai.input.messages", + "gen_ai.output.messages", + "gen_ai.system_instructions", + "gen_ai.prompt", + "gen_ai.completion", + "gen_ai.tool.call.arguments", + "gen_ai.tool.call.result", + "gen_ai.retrieval.documents", + "gen_ai.retrieval.query.text", + "gen_ai.memory.query.text", + "gen_ai.memory.records", + "gen_ai.evaluation.explanation", + # Traceloop / OpenLLMetry + "traceloop.entity.input", + "traceloop.entity.output", + "traceloop.prompt.template", + "gen_ai.task.input", + "gen_ai.task.output", + "gen_ai.guardrail.input", + "gen_ai.guardrail.output", + "gen_ai.guardrail.error.message", + "gen_ai.response.revised_prompt", + "mcp.response.value", + "db.marqo.search.query", + # Anthropic + "anthropic.tool_result.content", + # Pydantic AI / Logfire + "pydantic_ai.all_messages", + "request_data", + "response_data", + "model_request_parameters", + "final_result", + "tool_arguments", + "tool_response", + "inputs", + "prompt", + "events", + # OpenInference + "input.value", + "output.value", + "llm.function_call", + "llm.prompt_template.template", + "llm.prompt_template.variables", + # Holds request parameters such as Responses `instructions`. + "llm.invocation_parameters", + "reranker.query", + # Langfuse + "langfuse.trace.input", + "langfuse.trace.output", + "langfuse.observation.input", + "langfuse.observation.output", + "langfuse.observation.status_message", + "langfuse.experiment.item.expected_output", + # Errors + "exception.message", + "exception.stacktrace", + "error.message", + } +) + +_CONTENT_ATTRIBUTE_PREFIXES = ( + "gen_ai.prompt.", + "gen_ai.completion.", + "llm.prompts.", + "llm.completions.", + "llm.input_messages.", + "llm.output_messages.", + "llm.choices.", + "traceloop.prompt.template_variables.", +) + +_CONTENT_ATTRIBUTE_PATTERN = re.compile( + "|".join( + ( + r"embedding\.embeddings\.\d+\.embedding\.(text|vector)", + r"retrieval\.documents\.\d+\.document\.(content|metadata)", + r"reranker\.(input|output)_documents\.\d+\.document\.content", + r"(input|output)\.images\.\d+\.image\.url", + r"db\.chroma\.(query|get|delete)\.where_document", + r"mastra\.[a-z_]+\.(input|output)", + ) + ) +) + +# GenAI semantic convention prompt metadata, not content, under the `gen_ai.prompt.` prefix. +_NON_CONTENT_ATTRIBUTES = frozenset({"gen_ai.prompt.name", "gen_ai.prompt.version"}) + +_PROVIDER_METADATA_ATTRIBUTE = "ai.response.providerMetadata" + +# Tool definitions stay under privacy, but instrumentations serialize remote MCP tools +# with their credentials into them. +_TOOL_DEFINITION_ATTRIBUTES = frozenset({"gen_ai.tool.definitions", "ai.prompt.tools"}) +_TOOL_DEFINITION_PATTERN = re.compile(r"llm\.tools\.\d+\.tool\.json_schema") + +_EXCEPTION_EVENT_NAME = "exception" + + +def _is_content_attribute(key: str) -> bool: + """Whether a span or event attribute can carry prompts, completions, tool data or error text.""" + if key in _NON_CONTENT_ATTRIBUTES: + return False + return ( + key in _CONTENT_ATTRIBUTES + or key.startswith(_CONTENT_ATTRIBUTE_PREFIXES) + or f"{key}." in _CONTENT_ATTRIBUTE_PREFIXES + or _CONTENT_ATTRIBUTE_PATTERN.fullmatch(key) is not None + ) + + +def _otel_privacy_mode_enabled(privacy_mode: bool, client: Optional["Client"]) -> bool: + """Explicit flag, else the given client's `privacy_mode`, else the module-level configuration.""" + if privacy_mode: + return True + if client is not None: + return getattr(client, "privacy_mode", False) is True + # Imported here so later changes to the module-level settings still apply. + from ... import default_client, privacy_mode as global_privacy_mode + + return bool(global_privacy_mode) or ( + getattr(default_client, "privacy_mode", False) is True + ) + + +def _gateway_cost_only(value: Any) -> Optional[str]: + """ + Provider metadata can carry content, such as Google grounding search queries. + Ingestion only reads the Vercel AI Gateway cost from it. + """ + if not isinstance(value, str): + return None + try: + metadata = json.loads(value) + except ValueError: + return None + gateway = metadata.get("gateway") if isinstance(metadata, dict) else None + cost = gateway.get("cost") if isinstance(gateway, dict) else None + if isinstance(cost, bool) or not isinstance(cost, (int, float, str)): + return None + return json.dumps({"gateway": {"cost": cost}}, separators=(",", ":")) + + +def _redact_tool_definition_json(value: str, drop_unparseable: bool) -> Optional[str]: + try: + parsed = json.loads(value) + except ValueError: + parsed = None + if isinstance(parsed, list): + redacted = _redact_mcp_tool_credentials(parsed) + elif isinstance(parsed, dict): + redacted = _redact_mcp_tool(parsed) + else: + return None if drop_unparseable else value + return value if redacted is parsed else json.dumps(redacted) + + +def _redact_tool_definitions( + value: Any, drop_unparseable: bool +) -> Optional[AttributeValue]: + """ + Mask remote MCP credentials in a tool definition attribute. Under privacy mode a + value that cannot be parsed is dropped, because it could still hold credentials. + """ + if isinstance(value, str): + return _redact_tool_definition_json(value, drop_unparseable) + if not isinstance(value, (list, tuple)): + return None if drop_unparseable else value + redacted: List[Any] = [] + for item in value: + definition = ( + _redact_tool_definition_json(item, drop_unparseable) + if isinstance(item, str) + else None + if drop_unparseable + else item + ) + if definition is not None: + redacted.append(definition) + if len(redacted) == len(value) and all(a is b for a, b in zip(redacted, value)): + return value + return redacted + + +def _is_tool_definition_attribute(key: str) -> bool: + return ( + key in _TOOL_DEFINITION_ATTRIBUTES + or _TOOL_DEFINITION_PATTERN.fullmatch(key) is not None + ) + + +def _without_content(attributes: Attributes) -> BoundedAttributes: + kept: Dict[str, Any] = {} + for key, value in (attributes or {}).items(): + if _is_content_attribute(key): + continue + if _is_tool_definition_attribute(key): + definitions = _redact_tool_definitions(value, drop_unparseable=True) + if definitions is not None: + kept[key] = definitions + continue + if key == _PROVIDER_METADATA_ATTRIBUTE: + metadata = _gateway_cost_only(value) + if metadata is not None: + kept[key] = metadata + continue + kept[key] = value + return BoundedAttributes(attributes=kept) + + +def _without_content_span(span: ReadableSpan) -> ReadableSpan: + """ + Return a copy of ``span`` without content attributes, non-exception events, + exception messages and stack traces, or the status description. + """ + # Older OTLP encoders read `.dropped` from these collections, so plain dicts and + # lists break export. + events = BoundedList.from_seq( + None, + [ + Event( + name=event.name, + attributes=_without_content(event.attributes), + timestamp=event.timestamp, + ) + # Other span events (older GenAI semconv messages, framework logs) carry + # content and PostHog ingestion only reads exception events. + for event in span.events + if event.name == _EXCEPTION_EVENT_NAME + ], + ) + return ReadableSpan( + name=span.name, + context=span.get_span_context(), + parent=span.parent, + resource=span.resource, + attributes=_without_content(span.attributes), + events=events, + links=BoundedList.from_seq(None, span.links), + kind=span.kind, + status=Status(span.status.status_code), + start_time=span.start_time, + end_time=span.end_time, + instrumentation_scope=span.instrumentation_scope, + ) + + +def _without_mcp_credentials_span(span: ReadableSpan) -> ReadableSpan: + """Return ``span`` with remote MCP credentials masked in its tool definitions.""" + attributes = dict(span.attributes or {}) + redacted: Dict[str, Any] = {} + for key, value in attributes.items(): + if not _is_tool_definition_attribute(key): + redacted[key] = value + continue + definitions = _redact_tool_definitions(value, drop_unparseable=False) + if definitions is not None: + redacted[key] = definitions + if redacted.keys() == attributes.keys() and all( + redacted[key] is attributes[key] for key in attributes + ): + return span + return ReadableSpan( + name=span.name, + context=span.get_span_context(), + parent=span.parent, + resource=span.resource, + attributes=BoundedAttributes(attributes=redacted), + events=BoundedList.from_seq(None, span.events), + links=BoundedList.from_seq(None, span.links), + kind=span.kind, + status=span.status, + start_time=span.start_time, + end_time=span.end_time, + instrumentation_scope=span.instrumentation_scope, + ) + + +def _redact_span(span: ReadableSpan, privacy_mode: bool) -> ReadableSpan: + """ + Mask remote MCP credentials in tool definitions on every export, as for + ``$ai_tools``. Privacy mode also removes all content. + """ + if privacy_mode: + return _without_content_span(span) + return _without_mcp_credentials_span(span) + + +def _redacted_spans( + spans: Sequence[ReadableSpan], privacy_mode: bool +) -> List[ReadableSpan]: + """Redact each span, dropping only a span whose redaction fails rather than leaking it.""" + redacted = [] + for span in spans: + try: + redacted.append(_redact_span(span, privacy_mode)) + except Exception: + log.debug("Failed to redact span, dropping it", exc_info=True) + return redacted diff --git a/posthog/ai/otel/exporter.py b/posthog/ai/otel/exporter.py index ec6e387f..f5d21183 100644 --- a/posthog/ai/otel/exporter.py +++ b/posthog/ai/otel/exporter.py @@ -5,15 +5,19 @@ SpanExporter (e.g. as an argument to BatchSpanProcessor). """ -from typing import Optional, Sequence +from typing import TYPE_CHECKING, Optional, Sequence from opentelemetry.sdk.trace import ReadableSpan from opentelemetry.sdk.trace.export import SpanExporter, SpanExportResult from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter from ..gateway import warn_if_posthog_ai_gateway_otel_attributes +from ._privacy import _otel_privacy_mode_enabled, _redacted_spans from .spans import DEFAULT_HOST, is_ai_span +if TYPE_CHECKING: + from ...client import Client + class PostHogTraceExporter(SpanExporter): """Span exporter that filters AI spans and forwards them to PostHog. @@ -41,14 +45,23 @@ def __init__( self, api_key: str, host: str = DEFAULT_HOST, + *, + privacy_mode: bool = False, + client: Optional["Client"] = None, ): """ Args: api_key: PostHog project API key. host: PostHog host URL. Defaults to US cloud. + privacy_mode: Drop prompts, completions, tool arguments and results, + and error messages from exported spans. + client: PostHog client whose ``privacy_mode`` also applies. Without + one, the module-level ``posthog.privacy_mode`` setting applies. """ self._api_key = api_key self._host = host.rstrip("/") + self._privacy_mode = privacy_mode + self._client = client self._exporter = OTLPSpanExporter( endpoint=f"{self._host}/i/v0/ai/otel", @@ -70,6 +83,8 @@ def export(self, spans: Sequence[ReadableSpan]) -> SpanExportResult: return SpanExportResult.SUCCESS for span in ai_spans: warn_if_posthog_ai_gateway_otel_attributes(span.attributes) + privacy_mode = _otel_privacy_mode_enabled(self._privacy_mode, self._client) + ai_spans = _redacted_spans(ai_spans, privacy_mode) return self._exporter.export(ai_spans) def shutdown(self) -> None: diff --git a/posthog/ai/otel/processor.py b/posthog/ai/otel/processor.py index 31073c54..9c3e7397 100644 --- a/posthog/ai/otel/processor.py +++ b/posthog/ai/otel/processor.py @@ -9,7 +9,7 @@ import os import threading import weakref -from typing import Dict, Optional, Tuple +from typing import TYPE_CHECKING, Dict, Optional, Tuple from opentelemetry.context import Context from opentelemetry.sdk.trace import ReadableSpan, Span, SpanProcessor @@ -22,8 +22,11 @@ capture_request_context, with_request_context, ) +from ._privacy import _otel_privacy_mode_enabled, _redacted_spans from .spans import DEFAULT_HOST, is_ai_span +if TYPE_CHECKING: + from ...client import Client log = logging.getLogger("posthog") @@ -89,14 +92,23 @@ def __init__( self, api_key: str, host: str = DEFAULT_HOST, + *, + privacy_mode: bool = False, + client: Optional["Client"] = None, ): """ Args: api_key: PostHog project API key. host: PostHog host URL. Defaults to US cloud. + privacy_mode: Drop prompts, completions, tool arguments and results, + and error messages from exported spans. + client: PostHog client whose ``privacy_mode`` also applies. Without + one, the module-level ``posthog.privacy_mode`` setting applies. """ self._api_key = api_key self._host = host.rstrip("/") + self._privacy_mode = privacy_mode + self._client = client exporter = OTLPSpanExporter( endpoint=f"{self._host}/i/v0/ai/otel", @@ -164,7 +176,9 @@ def on_end(self, span: ReadableSpan) -> None: span = with_request_context(span, captured) except Exception: log.debug("Failed to add PostHog context to span", exc_info=True) - self._processor.on_end(span) + privacy_mode = _otel_privacy_mode_enabled(self._privacy_mode, self._client) + for redacted in _redacted_spans([span], privacy_mode): + self._processor.on_end(redacted) def shutdown(self) -> None: """Shut down the underlying batch span processor.""" diff --git a/posthog/ai/utils.py b/posthog/ai/utils.py index 595a58ad..8feafbb6 100644 --- a/posthog/ai/utils.py +++ b/posthog/ai/utils.py @@ -1,5 +1,7 @@ import asyncio +import copy import logging +import re import time import uuid from typing import Any, Callable, Dict, List, Optional, Tuple, cast @@ -537,7 +539,7 @@ def call_llm_and_track_usage( http_status = _get_http_status(exc) # 0 because its likely an SDK error error_params = { "$ai_is_error": True, - "$ai_error": exc.__str__(), + "$ai_error": _format_ai_error(ph_client, posthog_privacy_mode, exc), } # TODO: Add exception capture for OpenAI/Anthropic/Gemini wrappers when # enable_exception_autocapture is True, similar to LangChain callbacks. @@ -707,7 +709,7 @@ async def call_llm_and_track_usage_async( http_status = _get_http_status(exc) # 0 because its likely an SDK error error_params = { "$ai_is_error": True, - "$ai_error": exc.__str__(), + "$ai_error": _format_ai_error(ph_client, posthog_privacy_mode, exc), } # TODO: Add exception capture for OpenAI/Anthropic/Gemini wrappers when # enable_exception_autocapture is True, similar to LangChain callbacks. @@ -850,12 +852,229 @@ def finalize_ai_content(value: Any, ph_client: Any = None) -> Any: return redact_media(value, ph_client=ph_client) +def _privacy_mode_enabled(ph_client: Any, privacy_mode: Optional[bool]) -> bool: + return bool(getattr(ph_client, "privacy_mode", False) or privacy_mode) + + def with_privacy_mode(ph_client: PostHogClient, privacy_mode: bool, value: Any): - if getattr(ph_client, "privacy_mode", False) or privacy_mode: + if _privacy_mode_enabled(ph_client, privacy_mode): return None return value +_ERROR_CLASS_NAME_RE = re.compile(r"^[A-Za-z_][A-Za-z0-9_.]{0,127}$") +_ERROR_PROVIDER_CODE_RE = re.compile(r"^[A-Za-z0-9_.:-]{1,64}$") +_ERROR_PARAM_RE = re.compile(r"^[A-Za-z0-9_.\[\]-]{1,128}$") +_ERROR_REQUEST_ID_RE = re.compile(r"^[A-Za-z0-9_.:-]{1,128}$") +_ERROR_CODE_FIELDS = ("code", "type", "status") +_ERROR_STATUS_FIELDS = ("status_code", "status", "code") +_MAX_ERROR_CAUSES = 3 +_FALLBACK_ERROR_CLASS_NAME = "Exception" + + +def _read_error_field(source: Any, key: str) -> Any: + if source is None: + return None + try: + if isinstance(source, dict): + return source.get(key) + return getattr(source, key, None) + except Exception: + return None + + +def _matching(value: object, grammar: "re.Pattern[str]") -> Optional[str]: + if isinstance(value, str) and grammar.fullmatch(value): + return value + return None + + +def _first_matching(values: List[Any], grammar: "re.Pattern[str]") -> Optional[str]: + for value in values: + match = _matching(value, grammar) + if match is not None: + return match + return None + + +def _error_class_name(name: object) -> str: + return _matching(name, _ERROR_CLASS_NAME_RE) or _FALLBACK_ERROR_CLASS_NAME + + +def _error_http_status(exc: BaseException) -> Optional[int]: + for key in _ERROR_STATUS_FIELDS: + value = _read_error_field(exc, key) + if ( + isinstance(value, int) + and not isinstance(value, bool) + and 100 <= value <= 599 + ): + return value + return None + + +def _error_body(exc: BaseException) -> Any: + # Anthropic nests the detail as `{"type": "error", "error": {"type": ...}}`. + body = _read_error_field(exc, "body") + inner = _read_error_field(body, "error") + return inner if isinstance(inner, dict) else body + + +def _error_cause_names(exc: BaseException) -> List[str]: + names: List[str] = [] + cause = exc.__cause__ or exc.__context__ + while cause is not None and len(names) < _MAX_ERROR_CAUSES: + names.append(_error_class_name(type(cause).__name__)) + cause = cause.__cause__ or cause.__context__ + return names + + +def _format_error_summary( + name: object, + *, + status: Optional[int] = None, + codes: Optional[List[str]] = None, + param: Optional[str] = None, + request_id: Optional[str] = None, + causes: Optional[List[str]] = None, +) -> str: + """Render the privacy-safe `$ai_error` format shared with the JS SDK.""" + summary = _error_class_name(name) + if status is not None: + summary += f": {status}" + for code in dict.fromkeys(codes or []): + summary += f" {code}" + if param is not None: + summary += f" param={param}" + if request_id is not None: + summary += f" request_id={request_id}" + if causes: + summary += f" (caused by {', '.join(causes)})" + return summary + + +def _summarize_error_for_privacy(exc: BaseException) -> str: + """ + Describe an exception for `$ai_error` without its message, traceback or + arbitrary attributes, which routinely echo prompts and model output. + Only structured fields that pass a strict grammar are kept. + """ + try: + sources = [exc, _error_body(exc)] + codes = [ + code + for source in sources + for key in _ERROR_CODE_FIELDS + if ( + code := _matching( + _read_error_field(source, key), _ERROR_PROVIDER_CODE_RE + ) + ) + is not None + ] + return _format_error_summary( + type(exc).__name__, + status=_error_http_status(exc), + codes=codes, + param=_first_matching( + [_read_error_field(source, "param") for source in sources], + _ERROR_PARAM_RE, + ), + request_id=_matching( + _read_error_field(exc, "request_id"), _ERROR_REQUEST_ID_RE + ), + causes=_error_cause_names(exc), + ) + except Exception: + return _FALLBACK_ERROR_CLASS_NAME + + +_PRIVACY_SAFE_MESSAGE_ATTR = "_posthog_privacy_safe_message" + + +def _with_privacy_safe_message(exc: BaseException) -> BaseException: + """Mark an SDK-built exception whose message is fixed text, so privacy mode keeps it.""" + setattr(exc, _PRIVACY_SAFE_MESSAGE_ATTR, True) + return exc + + +def _format_ai_error( + ph_client: Any, + privacy_mode: Optional[bool], + exc: BaseException, + describe: Callable[[BaseException], str] = str, +) -> str: + """The `$ai_error` value for `exc`, summarized when privacy mode is on.""" + if _privacy_mode_enabled(ph_client, privacy_mode) and ( + getattr(exc, _PRIVACY_SAFE_MESSAGE_ATTR, False) is not True + ): + return _summarize_error_for_privacy(exc) + return describe(exc) + + +_MCP_TOOL_CREDENTIAL_FIELDS = ("headers", "authorization") +_REDACTED_MCP_CREDENTIAL = "[REDACTED]" + +# Vercel AI SDK provider tools that configure a remote MCP server, keyed by their `id`. +# They reach this SDK through OpenTelemetry tool definition attributes. +_MCP_PROVIDER_TOOL_IDS = frozenset({"openai.mcp", "xai.mcp", "google.mcp_server"}) + + +def _without_mcp_credentials(config: Dict[str, Any]) -> Dict[str, Any]: + return { + key: ( + _REDACTED_MCP_CREDENTIAL + if key in _MCP_TOOL_CREDENTIAL_FIELDS and value is not None + else value + ) + for key, value in config.items() + } + + +def _is_mcp_provider_tool(tool: Dict[str, Any]) -> bool: + return ( + tool.get("type") in ("provider", "provider-defined") + and tool.get("id") in _MCP_PROVIDER_TOOL_IDS + and isinstance(tool.get("args"), dict) + ) + + +def _redact_mcp_tool(tool: Any) -> Any: + if not isinstance(tool, dict): + if getattr(tool, "type", None) != "mcp": + return tool + model_dump = getattr(tool, "model_dump", None) + if callable(model_dump): + return _redact_mcp_tool(model_dump()) + try: + redacted = copy.copy(tool) + for field in _MCP_TOOL_CREDENTIAL_FIELDS: + if getattr(tool, field, None) is not None: + setattr(redacted, field, _REDACTED_MCP_CREDENTIAL) + return redacted + except Exception: + return {"type": "mcp"} + if tool.get("type") == "mcp": + return _without_mcp_credentials(tool) + if _is_mcp_provider_tool(tool): + return {**tool, "args": _without_mcp_credentials(tool["args"])} + return tool + + +def _redact_mcp_tool_credentials(tools: Any) -> Any: + """ + Mask the `headers` and `authorization` of MCP tool definitions, which carry + credentials for the remote server. Returns copies; the caller's tools are + left untouched. + """ + if not isinstance(tools, (list, tuple)): + return tools + redacted = [_redact_mcp_tool(tool) for tool in tools] + if all(new is old for new, old in zip(redacted, tools)): + return tools + return redacted + + def capture_streaming_event( ph_client: PostHogClient, event_data: StreamingEventData, @@ -918,7 +1137,14 @@ def capture_streaming_event( "$ai_base_url": str(event_data["base_url"]), **(event_data.get("properties") or {}), **( - {"$ai_is_error": True, "$ai_error": str(error)} if error is not None else {} + { + "$ai_is_error": True, + "$ai_error": _format_ai_error( + ph_client, event_data["privacy_mode"], error + ), + } + if error is not None + else {} ), } diff --git a/posthog/test/ai/gemini/test_gemini_parity.py b/posthog/test/ai/gemini/test_gemini_parity.py index a3129903..c117ee78 100644 --- a/posthog/test/ai/gemini/test_gemini_parity.py +++ b/posthog/test/ai/gemini/test_gemini_parity.py @@ -36,7 +36,19 @@ def test_sync_and_async_clients_preserve_explicit_empty_api_key( @pytest.mark.parametrize("client_class", [Client, AsyncClient]) -def test_sync_and_async_clients_merge_posthog_defaults_without_mutation(client_class): +@pytest.mark.parametrize( + "default_privacy_mode, call_privacy_mode, expected_privacy_mode", + [ + (True, False, True), + (True, None, True), + (False, True, True), + (False, None, False), + (False, False, False), + ], +) +def test_sync_and_async_clients_merge_posthog_defaults_without_mutation( + client_class, default_privacy_mode, call_privacy_mode, expected_privacy_mode +): default_properties = {"shared": "default", "default-only": True} with patch.object(google_genai, "Client"): @@ -45,7 +57,7 @@ def test_sync_and_async_clients_merge_posthog_defaults_without_mutation(client_c posthog_client=MagicMock(), posthog_distinct_id="default-id", posthog_properties=default_properties, - posthog_privacy_mode=True, + posthog_privacy_mode=default_privacy_mode, posthog_groups={"organization": "default-org"}, ) @@ -53,7 +65,7 @@ def test_sync_and_async_clients_merge_posthog_defaults_without_mutation(client_c None, "", {"shared": "call", "call-only": True}, - False, + call_privacy_mode, None, ) @@ -61,7 +73,7 @@ def test_sync_and_async_clients_merge_posthog_defaults_without_mutation(client_c "default-id", "", {"shared": "call", "default-only": True, "call-only": True}, - False, + expected_privacy_mode, {"organization": "default-org"}, ) assert default_properties == {"shared": "default", "default-only": True} diff --git a/posthog/test/ai/langchain/test_callbacks.py b/posthog/test/ai/langchain/test_callbacks.py index e55c42bb..bf884260 100644 --- a/posthog/test/ai/langchain/test_callbacks.py +++ b/posthog/test/ai/langchain/test_callbacks.py @@ -2716,16 +2716,26 @@ def failing_span(_): assert span_props["$ai_error"] == "ValueError: test error" -def test_exception_autocapture_disabled_does_not_capture(): - """Test that capture_exception is NOT called when autocapture is disabled.""" +@pytest.mark.parametrize( + "autocapture, client_privacy_mode, handler_privacy_mode, expected_error", + [ + (False, False, False, "ValueError: test error"), + (True, True, False, "ValueError"), + (True, False, True, "ValueError"), + ], +) +def test_exception_autocapture_disabled_does_not_capture( + autocapture, client_privacy_mode, handler_privacy_mode, expected_error +): + """capture_exception is skipped when autocapture is off or privacy mode is on.""" mock_client = MagicMock() - mock_client.privacy_mode = False - mock_client.enable_exception_autocapture = False + mock_client.privacy_mode = client_privacy_mode + mock_client.enable_exception_autocapture = autocapture def failing_span(_): raise ValueError("test error") - callbacks = [CallbackHandler(mock_client)] + callbacks = [CallbackHandler(mock_client, privacy_mode=handler_privacy_mode)] chain = RunnableLambda(failing_span) try: @@ -2746,7 +2756,7 @@ def failing_span(_): span_props = span_calls[0][1]["properties"] assert "$exception_event_id" not in span_props - assert span_props["$ai_error"] == "ValueError: test error" + assert span_props["$ai_error"] == expected_error def test_exception_autocapture_on_llm_generation_error(mock_client): diff --git a/posthog/test/ai/langchain/test_middleware.py b/posthog/test/ai/langchain/test_middleware.py index 441cdc22..115821d7 100644 --- a/posthog/test/ai/langchain/test_middleware.py +++ b/posthog/test/ai/langchain/test_middleware.py @@ -641,7 +641,7 @@ def handler(inner_request: ToolCallRequest) -> ToolMessage: assert actual is response span = _event(client, "$ai_span")["properties"] assert span["$ai_is_error"] is True - assert secret not in str(span.get("$ai_error", "")) + assert span["$ai_error"] == "RuntimeError: Tool returned an error" @pytest.mark.parametrize("asynchronous", [False, True]) diff --git a/posthog/test/ai/test_privacy_mode.py b/posthog/test/ai/test_privacy_mode.py new file mode 100644 index 00000000..065c79b0 --- /dev/null +++ b/posthog/test/ai/test_privacy_mode.py @@ -0,0 +1,800 @@ +"""Privacy mode across the AI integrations: nothing user-authored may leave the process.""" + +import json +from types import SimpleNamespace +from typing import Any, Callable, Dict, Iterator, List +from unittest.mock import MagicMock, patch + +import pytest +from pydantic import BaseModel, ValidationError + +from posthog.ai.utils import ( + _REDACTED_MCP_CREDENTIAL, + extract_available_tool_calls, + _summarize_error_for_privacy, +) +from posthog.client import Client +from posthog.test.test_utils import FAKE_TEST_API_KEY + +CANARY = "CANARY_7f3a" + + +class _Parsed(BaseModel): + answer: int + + +def _validation_error() -> ValidationError: + try: + _Parsed.model_validate_json(json.dumps({"answer": CANARY})) + except ValidationError as exc: + return exc + raise AssertionError("validation unexpectedly passed") + + +def _httpx_response(status: int) -> Any: + import httpx + + return httpx.Response( + status, + request=httpx.Request("POST", "https://api.example.com/v1/run"), + headers={"x-request-id": "req_123", "request-id": "req_123"}, + ) + + +def _openai_bad_request() -> Exception: + import openai + + return openai.BadRequestError( + f"Error code: 400 - {CANARY}", + response=_httpx_response(400), + body={ + "message": f"echoed request: {CANARY}", + "type": "invalid_request_error", + "param": "messages[3].content", + "code": None, + }, + ) + + +def _anthropic_bad_request() -> Exception: + import anthropic + + return anthropic.BadRequestError( + f"echoed request: {CANARY}", + response=_httpx_response(400), + body={ + "type": "error", + "error": {"type": "invalid_request_error", "message": CANARY}, + }, + ) + + +def _gemini_client_error() -> Exception: + from google.genai import errors + + return errors.ClientError( + 400, + {"error": {"code": 400, "message": CANARY, "status": "INVALID_ARGUMENT"}}, + ) + + +class _ProviderShapedError(Exception): + def __init__(self, **fields: Any) -> None: + super().__init__(CANARY) + self.__dict__.update(fields) + + +class _UnreadableError(Exception): + @property + def status_code(self) -> int: + raise RuntimeError(CANARY) + + +def _chained_error(depth: int) -> BaseException: + error_types = [KeyError, ValueError, TypeError, LookupError, RuntimeError] + error: BaseException = error_types[0](CANARY) + for error_type in error_types[1 : depth + 1]: + try: + try: + raise error + except BaseException as cause: + raise error_type(CANARY) from cause + except BaseException as chained: + error = chained + return error + + +def _implicit_context_error() -> BaseException: + try: + try: + raise KeyError(CANARY) + except KeyError: + raise ValueError(CANARY) + except ValueError as exc: + return exc + + +@pytest.mark.parametrize( + "make_error, expected", + [ + pytest.param( + _openai_bad_request, + "BadRequestError: 400 invalid_request_error param=messages[3].content request_id=req_123", + id="openai", + ), + pytest.param( + _anthropic_bad_request, + "BadRequestError: 400 invalid_request_error request_id=req_123", + id="anthropic", + ), + pytest.param( + _gemini_client_error, "ClientError: 400 INVALID_ARGUMENT", id="gemini" + ), + pytest.param(_validation_error, "ValidationError", id="pydantic"), + pytest.param( + lambda: _ProviderShapedError( + code="invalid_request_error", + type="invalid_request_error", + param="x", + ), + "_ProviderShapedError invalid_request_error param=x", + id="no-status-and-duplicate-codes", + ), + pytest.param( + lambda: _ProviderShapedError(status_code=503, code="overloaded"), + "_ProviderShapedError: 503 overloaded", + id="generic-status-and-code", + ), + pytest.param( + lambda: _ProviderShapedError( + status_code=700, + code=f"{CANARY} with spaces", + type="x" * 65, + param="messages[0] content", + request_id="req/123", + ), + "_ProviderShapedError", + id="grammar-rejects-every-field", + ), + pytest.param( + lambda: _ProviderShapedError(status_code=True, status="429"), + "_ProviderShapedError 429", + id="bool-and-string-status-are-not-http-status", + ), + pytest.param( + lambda: type("Bad Name", (Exception,), {})(CANARY), + "Exception", + id="class-name-rejected", + ), + pytest.param( + lambda: _UnreadableError(CANARY), "_UnreadableError", id="raising-property" + ), + pytest.param( + lambda: _chained_error(2), + "TypeError (caused by ValueError, KeyError)", + id="explicit-cause", + ), + pytest.param( + _implicit_context_error, + "ValueError (caused by KeyError)", + id="implicit-context", + ), + pytest.param( + lambda: _chained_error(4), + "RuntimeError (caused by LookupError, TypeError, ValueError)", + id="cause-chain-capped-at-three", + ), + ], +) +def test_summarize_error_for_privacy( + make_error: Callable[[], BaseException], expected: str +) -> None: + summary = _summarize_error_for_privacy(make_error()) + + assert summary == expected + assert CANARY not in summary + + +@pytest.mark.parametrize("provider", ["openai", "anthropic", "gemini"]) +def test_mcp_tool_credentials_are_masked_without_mutating_caller_tools( + provider: str, +) -> None: + from openai.types.responses.tool import Mcp + + mcp_dict = { + "type": "mcp", + "server_label": "docs", + "server_url": "https://mcp.example.com", + "headers": {"Authorization": f"Bearer {CANARY}"}, + "authorization": CANARY, + } + mcp_model = Mcp( + type="mcp", + server_label="docs", + server_url="https://mcp.example.com", + headers={"X-Api-Key": CANARY}, + ) + mcp_object = SimpleNamespace(type="mcp", headers={"X-Api-Key": CANARY}) + function_tool = {"type": "function", "name": "lookup", "headers": "kept"} + tools = [mcp_dict, mcp_model, mcp_object, function_tool] + kwargs: Dict[str, Any] = ( + {"config": SimpleNamespace(tools=tools)} + if provider == "gemini" + else {"tools": tools} + ) + + captured = extract_available_tool_calls(provider, kwargs) + + assert captured[0] == { + **mcp_dict, + "headers": _REDACTED_MCP_CREDENTIAL, + "authorization": _REDACTED_MCP_CREDENTIAL, + } + assert captured[1]["headers"] == _REDACTED_MCP_CREDENTIAL + assert captured[1]["authorization"] is None + assert captured[1]["server_url"] == "https://mcp.example.com" + assert captured[2].headers == _REDACTED_MCP_CREDENTIAL + assert captured[3] is function_tool + assert CANARY not in json.dumps(captured, default=str) + assert tools == [mcp_dict, mcp_model, mcp_object, function_tool] + assert mcp_dict["headers"] == {"Authorization": f"Bearer {CANARY}"} + assert mcp_model.headers == {"X-Api-Key": CANARY} + assert mcp_object.headers == {"X-Api-Key": CANARY} + + +@pytest.fixture +def private_client() -> Iterator[Client]: + client = Client( + FAKE_TEST_API_KEY, privacy_mode=True, enable_exception_autocapture=True + ) + client._enqueue = MagicMock() # type: ignore[method-assign] + yield client + if client.exception_capture: + client.exception_capture.close() + + +def _sent_events(client: Client) -> List[Dict[str, Any]]: + return [call.args[0] for call in client._enqueue.call_args_list] # type: ignore[attr-defined] + + +def _assert_no_canary(client: Client) -> List[Dict[str, Any]]: + events = _sent_events(client) + assert events + assert any(event["properties"].get("$ai_is_error") for event in events) + assert CANARY not in json.dumps(events, default=str) + return events + + +def _function_tool() -> Dict[str, Any]: + return { + "type": "function", + "function": {"name": "lookup", "parameters": {"type": "object"}}, + } + + +@pytest.mark.parametrize( + "resource, method", + [("chat_create", "create"), ("chat_parse", "parse"), ("responses", "create")], +) +def test_openai_privacy_mode_sends_no_content( + private_client: Client, resource: str, method: str +) -> None: + import openai + + from posthog.ai.openai import OpenAI + + error = _validation_error() if resource == "chat_parse" else _openai_bad_request() + target = ( + openai.resources.responses.Responses + if resource == "responses" + else openai.resources.chat.completions.Completions + ) + client = OpenAI(posthog_client=private_client, api_key="test-key") + call = ( + getattr(client.responses, method) + if resource == "responses" + else getattr(client.chat.completions, method) + ) + request: Dict[str, Any] = ( + { + "input": f"user prompt {CANARY}", + "instructions": CANARY, + "tools": [ + { + "type": "mcp", + "server_label": "docs", + "headers": {"Authorization": CANARY}, + } + ], + } + if resource == "responses" + else { + "messages": [{"role": "user", "content": f"user prompt {CANARY}"}], + "tools": [_function_tool()], + } + ) + + with patch.object(target, method, side_effect=error): + with pytest.raises(type(error)): + call(model="gpt-4o", **request) + + _assert_no_canary(private_client) + + +def test_anthropic_privacy_mode_sends_no_content(private_client: Client) -> None: + from posthog.ai.anthropic import Anthropic + + client = Anthropic(api_key="test-key", posthog_client=private_client) + + with patch( + "anthropic.resources.Messages.create", side_effect=_anthropic_bad_request() + ): + with pytest.raises(Exception): + client.messages.create( + model="claude-sonnet-4-5", + max_tokens=16, + system=CANARY, + messages=[{"role": "user", "content": f"user prompt {CANARY}"}], + ) + + events = _assert_no_canary(private_client) + assert ( + events[0]["properties"]["$ai_error"] + == "BadRequestError: 400 invalid_request_error request_id=req_123" + ) + + +@pytest.mark.parametrize("method", ["generate_content", "embed_content"]) +def test_gemini_privacy_mode_sends_no_content( + private_client: Client, method: str +) -> None: + google_genai = pytest.importorskip("google.genai") + from posthog.ai.gemini import Client as GeminiClient + + error = ( + _validation_error() if method == "generate_content" else _gemini_client_error() + ) + client = GeminiClient(api_key="test-key", posthog_client=private_client) + + with patch.object(google_genai.models.Models, method, side_effect=error): + with pytest.raises(type(error)): + getattr(client.models, method)( + model="gemini-2.5-flash", + contents=[f"user prompt {CANARY}"], + posthog_privacy_mode=False, + ) + + _assert_no_canary(private_client) + + +def test_langchain_privacy_mode_sends_no_content_or_exception_event( + private_client: Client, +) -> None: + import uuid + + from langchain_core.exceptions import OutputParserException + from langchain_core.messages import HumanMessage + from langchain_core.prompts import ChatPromptTemplate + from langchain_core.runnables import RunnableLambda + + from posthog.ai.langchain import CallbackHandler + + callbacks = CallbackHandler(private_client) + + def failing_parser(_: Any) -> None: + raise OutputParserException( + f"Could not parse output: {CANARY}", llm_output=CANARY + ) + + chain = ChatPromptTemplate.from_messages([("user", CANARY)]) | RunnableLambda( + failing_parser + ) + with pytest.raises(OutputParserException): + chain.invoke({}, config={"callbacks": [callbacks]}) + + run_id = uuid.uuid4() + callbacks.on_chat_model_start( + {"kwargs": {"model_name": "gpt-4o"}}, + [[HumanMessage(content=CANARY)]], + run_id=run_id, + invocation_params={ + "tools": [ + _function_tool(), + {"type": "mcp", "server_label": "docs", "headers": {"k": CANARY}}, + ] + }, + ) + callbacks.on_llm_error(_openai_bad_request(), run_id=run_id) + + events = _assert_no_canary(private_client) + assert "$exception" not in {event["event"] for event in events} + errors = {event["properties"].get("$ai_error") for event in events} + assert "OutputParserException" in errors + + +def test_openai_agents_privacy_mode_sends_no_content(private_client: Client) -> None: + pytest.importorskip("agents") + from agents.tracing.span_data import ( + FunctionSpanData, + GenerationSpanData, + SpeechSpanData, + TranscriptionSpanData, + ) + + from posthog.ai.openai_agents import PostHogTracingProcessor + + processor = PostHogTracingProcessor(client=private_client, distinct_id="user") + trace = MagicMock( + trace_id="trace_1", group_id=None, metadata={"customer_note": CANARY} + ) + trace.name = "workflow" + processor.on_trace_start(trace) + + span_datas = [ + GenerationSpanData( + input=[{"role": "user", "content": CANARY}], + output=[{"role": "assistant", "content": CANARY}], + model="gpt-4o", + ), + FunctionSpanData(name="lookup", input=CANARY, output=CANARY), + TranscriptionSpanData( + input="AAAA", + input_format="pcm", + output=CANARY, + model="gpt-4o-transcribe", + model_config={"prompt": CANARY, "language": "en"}, + ), + SpeechSpanData( + input=CANARY, + output=None, + output_format="pcm", + model="gpt-4o-mini-tts", + model_config={"instructions": CANARY, "voice": "alloy"}, + ), + ] + for index, span_data in enumerate(span_datas): + span = MagicMock( + trace_id="trace_1", + span_id=f"span_{index}", + parent_id=None, + started_at=None, + ended_at=None, + span_data=span_data, + error={"message": f"Tool failed: {CANARY}", "data": {"detail": CANARY}}, + ) + processor.on_span_start(span) + processor.on_span_end(span) + processor.on_trace_end(trace) + + events = _assert_no_canary(private_client) + audio_configs = [ + event["properties"]["model_config"] + for event in events + if "model_config" in event["properties"] + ] + assert audio_configs == [ + {"prompt": None, "language": "en"}, + {"instructions": None, "voice": "alloy"}, + ] + + +_KEPT_OTEL_ATTRIBUTES: Dict[str, Any] = { + "gen_ai.request.model": "gpt-4o", + "gen_ai.usage.input_tokens": 3, + "gen_ai.tool.definitions": '[{"name": "lookup"}]', + "llm.request.functions.0.name": "lookup", + "ai.prompt.tools": ('{"name": "lookup"}',), + "ai.prompt.toolChoice": '{"type": "auto"}', + "gen_ai.prompt.name": "support-reply", + "gen_ai.prompt.version": "3", +} + + +_MCP_OTEL_TOOL = { + "type": "mcp", + "server_label": "docs", + "headers": {"Authorization": CANARY}, + "authorization": CANARY, +} + +_VERCEL_MCP_OTEL_TOOL = { + "type": "provider", + "id": "openai.mcp", + "name": "docs", + "args": {"serverUrl": "https://mcp.example.com", "authorization": CANARY}, +} + +_MCP_OTEL_TOOL_ATTRIBUTES: Dict[str, Any] = { + "ai.prompt.tools": ( + '{"name": "lookup"}', + json.dumps(_MCP_OTEL_TOOL), + json.dumps(_VERCEL_MCP_OTEL_TOOL), + ), + "llm.tools.0.tool.json_schema": json.dumps(_MCP_OTEL_TOOL), + "llm.tools.1.tool.json_schema": f"not json {CANARY}", +} + +_REDACTED_MCP_OTEL_TOOL = json.dumps( + { + **_MCP_OTEL_TOOL, + "headers": _REDACTED_MCP_CREDENTIAL, + "authorization": _REDACTED_MCP_CREDENTIAL, + } +) + +_REDACTED_MCP_OTEL_TOOL_ATTRIBUTES: Dict[str, Any] = { + "ai.prompt.tools": ( + '{"name": "lookup"}', + _REDACTED_MCP_OTEL_TOOL, + json.dumps( + { + **_VERCEL_MCP_OTEL_TOOL, + "args": { + "serverUrl": "https://mcp.example.com", + "authorization": _REDACTED_MCP_CREDENTIAL, + }, + } + ), + ), + "llm.tools.0.tool.json_schema": _REDACTED_MCP_OTEL_TOOL, +} + + +def _emit_canary_span(provider: Any, provider_metadata: str) -> None: + from opentelemetry.trace import Status, StatusCode + + tracer = provider.get_tracer("test") + content_attributes = { + "gen_ai.input.messages": json.dumps([{"role": "user", "content": CANARY}]), + "gen_ai.output.messages": CANARY, + "gen_ai.system_instructions": CANARY, + "gen_ai.tool.call.arguments": CANARY, + "gen_ai.tool.call.result": CANARY, + "gen_ai.prompt.0.content": CANARY, + "gen_ai.completion.0.content": CANARY, + "traceloop.entity.input": CANARY, + "traceloop.entity.output": CANARY, + "ai.prompt": CANARY, + "ai.prompt.messages": CANARY, + "ai.response.text": CANARY, + "ai.response.object": CANARY, + "ai.response.toolCalls": CANARY, + "ai.response.reasoning": CANARY, + "ai.toolCall.args": CANARY, + "ai.toolCall.result": CANARY, + "ai.value": CANARY, + "ai.values": [CANARY], + "pydantic_ai.all_messages": CANARY, + "final_result": CANARY, + "tool_arguments": CANARY, + "tool_response": CANARY, + "request_data": CANARY, + "response_data": CANARY, + "anthropic.tool_result.content": CANARY, + "events": CANARY, + "error.message": CANARY, + "input.value": CANARY, + "output.value": CANARY, + "llm.input_messages.0.message.content": CANARY, + "llm.output_messages.0.message.content": CANARY, + "llm.choices.0.completion.text": CANARY, + "llm.function_call": CANARY, + "llm.invocation_parameters": json.dumps({"instructions": CANARY}), + "llm.prompt_template.template": CANARY, + "llm.prompt_template.variables": CANARY, + "embedding.embeddings.0.embedding.text": CANARY, + "embedding.embeddings.0.embedding.vector": (0.12, 0.34), + "llm.prompts": CANARY, + "llm.input_messages": CANARY, + "ai.request.text": CANARY, + "ai.documents": CANARY, + "ai.response.reasoningDetails": CANARY, + "ai.response.files": CANARY, + "ai.decision.state": CANARY, + "ai.decision.questions": CANARY, + "ai.decision.answers": CANARY, + "ai.embedding": CANARY, + "ai.embeddings": CANARY, + "gen_ai.prompt.variable.customer": CANARY, + "gen_ai.retrieval.documents": CANARY, + "gen_ai.retrieval.query.text": CANARY, + "gen_ai.memory.query.text": CANARY, + "gen_ai.memory.records": CANARY, + "gen_ai.evaluation.explanation": CANARY, + "gen_ai.task.input": CANARY, + "gen_ai.task.output": CANARY, + "gen_ai.guardrail.input": CANARY, + "gen_ai.guardrail.output": CANARY, + "gen_ai.guardrail.error.message": CANARY, + "gen_ai.response.revised_prompt": CANARY, + "mcp.response.value": CANARY, + "traceloop.prompt.template": CANARY, + "traceloop.prompt.template_variables.customer": CANARY, + "db.marqo.search.query": CANARY, + "db.chroma.query.where_document": CANARY, + "llm.completions.0.content": CANARY, + "retrieval.documents.0.document.content": CANARY, + "retrieval.documents.0.document.metadata": CANARY, + "reranker.query": CANARY, + "reranker.input_documents.0.document.content": CANARY, + "reranker.output_documents.0.document.content": CANARY, + "input.images.0.image.url": CANARY, + "output.images.0.image.url": CANARY, + "model_request_parameters": CANARY, + "inputs": CANARY, + "prompt": CANARY, + "langfuse.trace.input": CANARY, + "langfuse.trace.output": CANARY, + "langfuse.observation.input": CANARY, + "langfuse.observation.output": CANARY, + "langfuse.observation.status_message": CANARY, + "langfuse.experiment.item.expected_output": CANARY, + "mastra.agent_run.input": CANARY, + "mastra.agent_run.output": CANARY, + } + with tracer.start_as_current_span( + "gen_ai.chat", + attributes={ + **_KEPT_OTEL_ATTRIBUTES, + **_MCP_OTEL_TOOL_ATTRIBUTES, + **content_attributes, + "ai.response.providerMetadata": provider_metadata, + }, + ) as span: + span.add_event("gen_ai.content.prompt", {"gen_ai.prompt": CANARY}) + span.record_exception(ValueError(CANARY)) + span.set_status(Status(StatusCode.ERROR, CANARY)) + + +def _export_spans( + integration: str, options: Dict[str, Any], emit: Callable[[Any], None] +) -> List[Any]: + from opentelemetry.sdk.trace import ReadableSpan, TracerProvider + from opentelemetry.sdk.trace.export import SimpleSpanProcessor + + from posthog.ai.otel import PostHogSpanProcessor, PostHogTraceExporter + + exported: List[ReadableSpan] = [] + provider = TracerProvider() + with ( + patch("posthog.ai.otel.processor.OTLPSpanExporter"), + patch("posthog.ai.otel.processor.BatchSpanProcessor") as batch, + patch("posthog.ai.otel.exporter.OTLPSpanExporter") as otlp, + ): + batch.return_value.on_end.side_effect = exported.append + otlp.return_value.export.side_effect = exported.extend + if integration == "processor": + provider.add_span_processor( + PostHogSpanProcessor(api_key="phc_test", **options) + ) + else: + provider.add_span_processor( + SimpleSpanProcessor(PostHogTraceExporter(api_key="phc_test", **options)) + ) + emit(provider) + return exported + + +@pytest.mark.parametrize( + "provider_metadata, kept_provider_metadata", + [ + ( + json.dumps( + { + "gateway": {"cost": 0.0012, "routing": CANARY}, + "google": {"groundingMetadata": CANARY}, + } + ), + {"ai.response.providerMetadata": '{"gateway":{"cost":0.0012}}'}, + ), + (json.dumps({"google": {"groundingMetadata": CANARY}}), {}), + (f"not json {CANARY}", {}), + ], + ids=["gateway_cost", "no_gateway_cost", "unparseable"], +) +@pytest.mark.parametrize("integration", ["processor", "exporter"]) +@pytest.mark.parametrize("privacy_source", ["option", "client", "module"]) +def test_otel_privacy_mode_sends_no_content( + integration: str, + privacy_source: str, + provider_metadata: str, + kept_provider_metadata: Dict[str, str], + monkeypatch: pytest.MonkeyPatch, +) -> None: + pytest.importorskip("opentelemetry.exporter.otlp.proto.http") + import posthog + from opentelemetry.exporter.otlp.proto.common.trace_encoder import encode_spans + + monkeypatch.setattr(posthog, "privacy_mode", privacy_source == "module") + options: Dict[str, Any] = { + "privacy_mode": privacy_source == "option", + "client": ( + Client(FAKE_TEST_API_KEY, privacy_mode=True) + if privacy_source == "client" + else None + ), + } + [span] = _export_spans( + integration, + options, + lambda provider: _emit_canary_span(provider, provider_metadata), + ) + + assert CANARY.encode() not in encode_spans([span]).SerializeToString() + assert dict(span.attributes or {}) == { + **_KEPT_OTEL_ATTRIBUTES, + **_REDACTED_MCP_OTEL_TOOL_ATTRIBUTES, + **kept_provider_metadata, + } + [exception_event] = span.events + assert exception_event.name == "exception" + exception_attributes = dict(exception_event.attributes or {}) + assert exception_attributes["exception.type"] == "ValueError" + assert set(exception_attributes) <= {"exception.type", "exception.escaped"} + assert span.status.is_ok is False + assert span.status.description is None + + +@pytest.mark.parametrize("integration", ["processor", "exporter"]) +def test_otel_masks_mcp_credentials_without_privacy_mode( + integration: str, monkeypatch: pytest.MonkeyPatch +) -> None: + pytest.importorskip("opentelemetry.exporter.otlp.proto.http") + import posthog + from opentelemetry.exporter.otlp.proto.common.trace_encoder import encode_spans + + monkeypatch.setattr(posthog, "privacy_mode", False) + attributes = { + **_KEPT_OTEL_ATTRIBUTES, + **_MCP_OTEL_TOOL_ATTRIBUTES, + "gen_ai.input.messages": "user prompt", + } + + def emit(provider: Any) -> None: + with provider.get_tracer("test").start_as_current_span( + "gen_ai.chat", attributes=attributes + ): + pass + + [span] = _export_spans(integration, {"privacy_mode": False}, emit) + + encode_spans([span]) + assert dict(span.attributes or {}) == { + **attributes, + **_REDACTED_MCP_OTEL_TOOL_ATTRIBUTES, + "llm.tools.1.tool.json_schema": f"not json {CANARY}", + } + + +@pytest.mark.parametrize("privacy_mode", [True, False]) +def test_otel_exporter_drops_only_the_span_that_fails_redaction( + privacy_mode: bool, +) -> None: + pytest.importorskip("opentelemetry.exporter.otlp.proto.http") + from opentelemetry.sdk.trace import TracerProvider + + from posthog.ai.otel import PostHogTraceExporter + + tracer = TracerProvider().get_tracer("test") + broken = tracer.start_span( + "gen_ai.chat", attributes={"ai.prompt.tools": json.dumps(_MCP_OTEL_TOOL)} + ) + healthy = tracer.start_span( + "gen_ai.chat", attributes={"gen_ai.request.model": "gpt-4o"} + ) + broken.end() + healthy.end() + + with ( + patch("posthog.ai.otel.exporter.OTLPSpanExporter") as otlp, + # Deeply nested JSON raises RecursionError while parsing on some Python versions. + patch("posthog.ai.otel._privacy._redact_mcp_tool", side_effect=RecursionError), + ): + PostHogTraceExporter(api_key="phc_test", privacy_mode=privacy_mode).export( + [broken, healthy] + ) + + [spans] = otlp.return_value.export.call_args.args + assert [dict(span.attributes or {}) for span in spans] == [ + {"gen_ai.request.model": "gpt-4o"} + ] diff --git a/references/public_api_snapshot.txt b/references/public_api_snapshot.txt index e5245a7e..67cc7c4e 100644 --- a/references/public_api_snapshot.txt +++ b/references/public_api_snapshot.txt @@ -199,10 +199,12 @@ alias posthog.ai.openai_agents.processor.Client -> posthog.client.Client alias posthog.ai.openai_agents.processor.finalize_ai_content -> posthog.ai.utils.finalize_ai_content alias posthog.ai.otel.PostHogSpanProcessor -> posthog.ai.otel.processor.PostHogSpanProcessor alias posthog.ai.otel.PostHogTraceExporter -> posthog.ai.otel.exporter.PostHogTraceExporter +alias posthog.ai.otel.exporter.Client -> posthog.client.Client alias posthog.ai.otel.exporter.DEFAULT_HOST -> posthog.ai.otel.spans.DEFAULT_HOST alias posthog.ai.otel.exporter.is_ai_span -> posthog.ai.otel.spans.is_ai_span alias posthog.ai.otel.exporter.warn_if_posthog_ai_gateway_otel_attributes -> posthog.ai.gateway.warn_if_posthog_ai_gateway_otel_attributes alias posthog.ai.otel.is_ai_span -> posthog.ai.otel.spans.is_ai_span +alias posthog.ai.otel.processor.Client -> posthog.client.Client alias posthog.ai.otel.processor.DEFAULT_HOST -> posthog.ai.otel.spans.DEFAULT_HOST alias posthog.ai.otel.processor.is_ai_span -> posthog.ai.otel.spans.is_ai_span alias posthog.ai.otel.processor.warn_if_posthog_ai_gateway_otel_attributes -> posthog.ai.gateway.warn_if_posthog_ai_gateway_otel_attributes @@ -1246,8 +1248,8 @@ class posthog.ai.openai.openai_async.WrappedResponses class posthog.ai.openai.openai_providers.AsyncAzureOpenAI(posthog_client: Optional[PostHogClient] = None, **kwargs) class posthog.ai.openai.openai_providers.AzureOpenAI(posthog_client: Optional[PostHogClient] = None, **kwargs) class posthog.ai.openai_agents.processor.PostHogTracingProcessor(client: Optional[Client] = None, distinct_id: Optional[Union[str, Callable[[Trace], Optional[str]]]] = None, privacy_mode: bool = False, groups: Optional[Dict[str, Any]] = None, properties: Optional[Dict[str, Any]] = None) -class posthog.ai.otel.exporter.PostHogTraceExporter(api_key: str, host: str = DEFAULT_HOST) -class posthog.ai.otel.processor.PostHogSpanProcessor(api_key: str, host: str = DEFAULT_HOST) +class posthog.ai.otel.exporter.PostHogTraceExporter(api_key: str, host: str = DEFAULT_HOST, *, privacy_mode: bool = False, client: Optional[Client] = None) +class posthog.ai.otel.processor.PostHogSpanProcessor(api_key: str, host: str = DEFAULT_HOST, *, privacy_mode: bool = False, client: Optional[Client] = None) class posthog.ai.prompts.CachedPrompt(prompt: str, fetched_at: float, name: str, version: int, label: Optional[str] = None, config: Optional[Dict[str, Any]] = None) class posthog.ai.prompts.PromptFetchError(message: str, retry_after_seconds: Optional[float] = None) class posthog.ai.prompts.PromptResult(source: PromptSource, prompt: str, name: Optional[str] = None, version: Optional[int] = None, label: Optional[str] = None, config: Optional[Dict[str, Any]] = None) From 945d9459ee4969c8f24187e3b28359d7b2a736d8 Mon Sep 17 00:00:00 2001 From: "releaser-posthog-python[bot]" <262414804+releaser-posthog-python[bot]@users.noreply.github.com> Date: Fri, 23 Oct 2026 15:26:03 +0000 Subject: [PATCH 17/60] chore: Release v7.75.11 [skip ci] (cherry picked from commit fb9b66bcc1d679180f5aee9c1fe4397636074c75) --- .sampo/changesets/ai-privacy-mode-leaks.md | 12 ------------ openfeature-provider/CHANGELOG.md | 6 ++++++ openfeature-provider/pyproject.toml | 2 +- posthog/CHANGELOG.md | 13 +++++++++++++ posthog/version.py | 2 +- pyproject.toml | 2 +- uv.lock | 4 ++-- 7 files changed, 24 insertions(+), 17 deletions(-) delete mode 100644 .sampo/changesets/ai-privacy-mode-leaks.md diff --git a/.sampo/changesets/ai-privacy-mode-leaks.md b/.sampo/changesets/ai-privacy-mode-leaks.md deleted file mode 100644 index 8eb4ecec..00000000 --- a/.sampo/changesets/ai-privacy-mode-leaks.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -pypi/posthog: patch ---- - -Privacy mode now covers error text and the remaining content paths in the AI integrations. - -- `$ai_error` no longer carries the exception message when privacy mode is on. Provider errors and validation errors often quote the prompt or the model output, so the SDK now sends a summary built only from structured fields: the class name, HTTP status, provider error code, `param`, request ID and the class names of chained causes (for example `BadRequestError: 400 invalid_request_error param=messages[3].content request_id=req_123`). This applies to the OpenAI, Anthropic and Gemini wrappers (including Gemini embeddings), LangChain generations, spans and traces, and OpenAI Agents spans. -- The LangChain callback handler no longer sends an `$exception` event for a failed run when privacy mode is on. -- `$ai_tools` masks the `headers` and `authorization` of MCP tool definitions (`type: "mcp"`) with `[REDACTED]`, with or without privacy mode. The tools you pass in are not modified. -- Gemini: a per-call `posthog_privacy_mode=False` no longer turns off privacy mode set on the client, matching the OpenAI and Anthropic wrappers. -- OpenAI Agents: privacy mode now redacts `$ai_trace_metadata` and the `prompt` and `instructions` in transcription and speech span `model_config`. -- OpenTelemetry: `PostHogSpanProcessor` and `PostHogTraceExporter` accept `privacy_mode` and `client`, and also honor the client's (or the module-level) `privacy_mode`. When on, exported spans drop prompt, completion, tool argument and tool result attributes, non-exception span events, exception messages and stack traces, and the span status description. diff --git a/openfeature-provider/CHANGELOG.md b/openfeature-provider/CHANGELOG.md index 4ba230b6..5f930ed7 100644 --- a/openfeature-provider/CHANGELOG.md +++ b/openfeature-provider/CHANGELOG.md @@ -4,6 +4,12 @@ All notable changes to `openfeature-provider-posthog` are documented here. This file is maintained by [Sampo](https://github.com/bruits/sampo) from changesets in `.sampo/changesets/` that target `pypi/openfeature-provider-posthog`. +## 0.1.125 — 2026-10-23 + +### Patch changes + +- Updated dependencies: posthog@7.75.11 + ## 0.1.124 — 2026-10-23 ### Patch changes diff --git a/openfeature-provider/pyproject.toml b/openfeature-provider/pyproject.toml index 4819f434..7de2d5ca 100644 --- a/openfeature-provider/pyproject.toml +++ b/openfeature-provider/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "openfeature-provider-posthog" -version = "0.1.124" +version = "0.1.125" description = "Official PostHog provider for the OpenFeature Python SDK." readme = "README.md" authors = [{ name = "PostHog", email = "engineering@posthog.com" }] diff --git a/posthog/CHANGELOG.md b/posthog/CHANGELOG.md index 0b165616..32d74af1 100644 --- a/posthog/CHANGELOG.md +++ b/posthog/CHANGELOG.md @@ -1,5 +1,18 @@ # posthog +## 7.75.11 — 2026-10-23 + +### Patch changes + +- [04e6a4c](https://github.com/posthog/posthog-python/commit/04e6a4c1c4f15e4dd0d010dee5fa83244f9b6dd8) Privacy mode now covers error text and the remaining content paths in the AI integrations. + + - `$ai_error` no longer carries the exception message when privacy mode is on. Provider errors and validation errors often quote the prompt or the model output, so the SDK now sends a summary built only from structured fields: the class name, HTTP status, provider error code, `param`, request ID and the class names of chained causes (for example `BadRequestError: 400 invalid_request_error param=messages[3].content request_id=req_123`). This applies to the OpenAI, Anthropic and Gemini wrappers (including Gemini embeddings), LangChain generations, spans and traces, and OpenAI Agents spans. + - The LangChain callback handler no longer sends an `$exception` event for a failed run when privacy mode is on. + - `$ai_tools` masks the `headers` and `authorization` of MCP tool definitions (`type: "mcp"`) with `[REDACTED]`, with or without privacy mode. The tools you pass in are not modified. + - Gemini: a per-call `posthog_privacy_mode=False` no longer turns off privacy mode set on the client, matching the OpenAI and Anthropic wrappers. + - OpenAI Agents: privacy mode now redacts `$ai_trace_metadata` and the `prompt` and `instructions` in transcription and speech span `model_config`. + - OpenTelemetry: `PostHogSpanProcessor` and `PostHogTraceExporter` accept `privacy_mode` and `client`, and also honor the client's (or the module-level) `privacy_mode`. When on, exported spans drop prompt, completion, tool argument and tool result attributes, non-exception span events, exception messages and stack traces, and the span status description. — Thanks @carlos-marchal-ph! + ## 7.75.10 — 2026-10-23 ### Patch changes diff --git a/posthog/version.py b/posthog/version.py index 80d27f16..0d256d5f 100644 --- a/posthog/version.py +++ b/posthog/version.py @@ -1 +1 @@ -VERSION = "7.75.10" +VERSION = "7.75.11" diff --git a/pyproject.toml b/pyproject.toml index d9934210..b3003b75 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "posthog" -version = "7.75.10" +version = "7.75.11" description = "Integrate PostHog into any python application." authors = [{ name = "PostHog", email = "engineering@posthog.com" }] maintainers = [{ name = "PostHog", email = "engineering@posthog.com" }] diff --git a/uv.lock b/uv.lock index 96dd88a1..fe475d40 100644 --- a/uv.lock +++ b/uv.lock @@ -2656,7 +2656,7 @@ wheels = [ [[package]] name = "openfeature-provider-posthog" -version = "0.1.124" +version = "0.1.125" source = { editable = "openfeature-provider" } dependencies = [ { name = "openfeature-sdk" }, @@ -3015,7 +3015,7 @@ wheels = [ [[package]] name = "posthog" -version = "7.75.10" +version = "7.75.11" source = { editable = "." } dependencies = [ { name = "distro" }, From b010c1cad9eb16578aba36892d7ce5cc8f2e28ce Mon Sep 17 00:00:00 2001 From: "releaser-posthog-python[bot]" <262414804+releaser-posthog-python[bot]@users.noreply.github.com> Date: Fri, 23 Oct 2026 15:26:08 +0000 Subject: [PATCH 18/60] Update generated references (cherry picked from commit 16d618b1636e47dfcd3a530231fddf16defa6b81) --- .../posthog-python-references-7.75.11.json | 3548 +++++++++++++++++ .../posthog-python-references-latest.json | 2 +- 2 files changed, 3549 insertions(+), 1 deletion(-) create mode 100644 references/posthog-python-references-7.75.11.json diff --git a/references/posthog-python-references-7.75.11.json b/references/posthog-python-references-7.75.11.json new file mode 100644 index 00000000..00e8b9d0 --- /dev/null +++ b/references/posthog-python-references-7.75.11.json @@ -0,0 +1,3548 @@ +{ + "id": "posthog-python", + "hogRef": "0.3", + "info": { + "version": "7.75.11", + "id": "posthog-python", + "title": "PostHog Python SDK", + "description": "Integrate PostHog into any python application.", + "slugPrefix": "posthog-python", + "specUrl": "https://github.com/PostHog/posthog-python" + }, + "types": [ + { + "id": "FeatureFlag", + "name": "FeatureFlag", + "path": "posthog.types.FeatureFlag", + "properties": [ + { + "name": "key", + "type": "str", + "description": "Field: key" + }, + { + "name": "enabled", + "type": "bool", + "description": "Field: enabled" + }, + { + "name": "variant", + "type": "Optional[str]", + "description": "Field: variant" + }, + { + "name": "reason", + "type": "Optional[FlagReason]", + "description": "Field: reason" + }, + { + "name": "metadata", + "type": "Union[FlagMetadata, LegacyFlagMetadata]", + "description": "Field: metadata" + } + ], + "example": "" + }, + { + "id": "FeatureFlagResult", + "name": "FeatureFlagResult", + "path": "posthog.types.FeatureFlagResult", + "properties": [ + { + "name": "key", + "type": "str", + "description": "Field: key" + }, + { + "name": "enabled", + "type": "bool", + "description": "Field: enabled" + }, + { + "name": "variant", + "type": "Optional[str]", + "description": "Field: variant" + }, + { + "name": "payload", + "type": "Optional[Any]", + "description": "Field: payload" + }, + { + "name": "reason", + "type": "Optional[str]", + "description": "Field: reason" + } + ], + "example": "" + }, + { + "id": "FlagMetadata", + "name": "FlagMetadata", + "path": "posthog.types.FlagMetadata", + "properties": [ + { + "name": "id", + "type": "int", + "description": "Field: id" + }, + { + "name": "payload", + "type": "Optional[str]", + "description": "Field: payload" + }, + { + "name": "version", + "type": "int", + "description": "Field: version" + }, + { + "name": "description", + "type": "str", + "description": "Field: description" + }, + { + "name": "has_experiment", + "type": "Optional[bool]", + "description": "Field: has_experiment" + } + ], + "example": "" + }, + { + "id": "FlagReason", + "name": "FlagReason", + "path": "posthog.types.FlagReason", + "properties": [ + { + "name": "code", + "type": "str", + "description": "Field: code" + }, + { + "name": "condition_index", + "type": "Optional[int]", + "description": "Field: condition_index" + }, + { + "name": "description", + "type": "str", + "description": "Field: description" + } + ], + "example": "" + }, + { + "id": "FlagsAndPayloads", + "name": "FlagsAndPayloads", + "path": "posthog.types.FlagsAndPayloads", + "properties": [ + { + "name": "featureFlags", + "type": "Optional[dict[str, Union[bool, str]]]", + "description": "Field: featureFlags" + }, + { + "name": "featureFlagPayloads", + "type": "Optional[dict[str, Any]]", + "description": "Field: featureFlagPayloads" + } + ], + "example": "" + }, + { + "id": "FlagsResponse", + "name": "FlagsResponse", + "path": "posthog.types.FlagsResponse", + "properties": [ + { + "name": "flags", + "type": "dict[str, FeatureFlag]", + "description": "Field: flags" + }, + { + "name": "errorsWhileComputingFlags", + "type": "bool", + "description": "Field: errorsWhileComputingFlags" + }, + { + "name": "requestId", + "type": "str", + "description": "Field: requestId" + }, + { + "name": "quotaLimit", + "type": "Optional[list[str]]", + "description": "Field: quotaLimit" + }, + { + "name": "evaluatedAt", + "type": "Optional[int]", + "description": "Field: evaluatedAt" + }, + { + "name": "minimalFlagCalledEvents", + "type": "bool", + "description": "Field: minimalFlagCalledEvents" + } + ], + "example": "" + }, + { + "id": "LegacyFlagMetadata", + "name": "LegacyFlagMetadata", + "path": "posthog.types.LegacyFlagMetadata", + "properties": [ + { + "name": "payload", + "type": "Any", + "description": "Field: payload" + } + ], + "example": "" + }, + { + "id": "SendFeatureFlagsOptions", + "name": "SendFeatureFlagsOptions", + "path": "posthog.types.SendFeatureFlagsOptions", + "properties": [ + { + "name": "should_send", + "type": "bool", + "description": "Field: should_send" + }, + { + "name": "only_evaluate_locally", + "type": "Optional[bool]", + "description": "Field: only_evaluate_locally" + }, + { + "name": "person_properties", + "type": "Optional[dict[str, Any]]", + "description": "Field: person_properties" + }, + { + "name": "group_properties", + "type": "Optional[dict[str, dict[str, Any]]]", + "description": "Field: group_properties" + }, + { + "name": "flag_keys_filter", + "type": "Optional[list[str]]", + "description": "Field: flag_keys_filter" + } + ], + "example": "" + }, + { + "id": "OptionalCaptureArgs", + "name": "OptionalCaptureArgs", + "path": "posthog.args.OptionalCaptureArgs", + "properties": [ + { + "name": "distinct_id", + "type": "NotRequired[Union[Number, str, UUID, int, any]]", + "description": "Field: distinct_id" + }, + { + "name": "properties", + "type": "NotRequired[Optional[dict[str, Any]]]", + "description": "Field: properties" + }, + { + "name": "timestamp", + "type": "NotRequired[Union[datetime, str, any]]", + "description": "Field: timestamp" + }, + { + "name": "uuid", + "type": "NotRequired[Union[str, UUID, any]]", + "description": "Field: uuid" + }, + { + "name": "groups", + "type": "NotRequired[Optional[dict[str, str]]]", + "description": "Field: groups" + }, + { + "name": "flags", + "type": "NotRequired[Optional[ForwardRef('FeatureFlagEvaluations')]]", + "description": "Field: flags" + }, + { + "name": "send_feature_flags", + "type": "NotRequired[Union[bool, SendFeatureFlagsOptions, any]]", + "description": "Field: send_feature_flags" + }, + { + "name": "disable_geoip", + "type": "NotRequired[Optional[bool]]", + "description": "Field: disable_geoip" + }, + { + "name": "_property_allowlist", + "type": "NotRequired[Optional[frozenset[str]]]", + "description": "Field: _property_allowlist" + } + ], + "example": "" + }, + { + "id": "OptionalSetArgs", + "name": "OptionalSetArgs", + "path": "posthog.args.OptionalSetArgs", + "properties": [ + { + "name": "distinct_id", + "type": "NotRequired[Union[Number, str, UUID, int, any]]", + "description": "Field: distinct_id" + }, + { + "name": "properties", + "type": "NotRequired[Optional[dict[str, Any]]]", + "description": "Field: properties" + }, + { + "name": "timestamp", + "type": "NotRequired[Union[datetime, str, any]]", + "description": "Field: timestamp" + }, + { + "name": "uuid", + "type": "NotRequired[Union[str, UUID, any]]", + "description": "Field: uuid" + }, + { + "name": "disable_geoip", + "type": "NotRequired[Optional[bool]]", + "description": "Field: disable_geoip" + } + ], + "example": "" + }, + { + "id": "SendFeatureFlagsOptions", + "name": "SendFeatureFlagsOptions", + "path": "posthog.types.SendFeatureFlagsOptions", + "properties": [ + { + "name": "should_send", + "type": "bool", + "description": "Field: should_send" + }, + { + "name": "only_evaluate_locally", + "type": "Optional[bool]", + "description": "Field: only_evaluate_locally" + }, + { + "name": "person_properties", + "type": "Optional[dict[str, Any]]", + "description": "Field: person_properties" + }, + { + "name": "group_properties", + "type": "Optional[dict[str, dict[str, Any]]]", + "description": "Field: group_properties" + }, + { + "name": "flag_keys_filter", + "type": "Optional[list[str]]", + "description": "Field: flag_keys_filter" + } + ], + "example": "" + } + ], + "classes": [ + { + "id": "PostHog", + "title": "PostHog", + "description": "This is the SDK reference for the PostHog Python SDK. You can learn more about example usage in the [Python SDK documentation](/docs/libraries/python). You can also follow [Flask](/docs/libraries/flask) and [Django](/docs/libraries/django) guides to integrate PostHog into your project. For long-running applications, create one client during application startup and reuse it for the lifetime of the process. This keeps background queues predictable and makes shutdown flushing straightforward. Multiple clients are still supported for intentional multi-project or multi-host setups.", + "functions": [ + { + "id": "__init__", + "title": "Client", + "description": "Initialize a new PostHog client instance.", + "details": "", + "category": "Initialization", + "params": [ + { + "name": "project_api_key", + "description": "PostHog project API key/token.", + "isOptional": true, + "type": "str" + }, + { + "name": "host", + "description": "PostHog host. Defaults to the US ingestion endpoint when not set. App hosts such as ``https://us.posthog.com`` are mapped to the corresponding ingestion host.", + "isOptional": false, + "type": "any" + }, + { + "name": "debug", + "description": "Enable verbose SDK logging and re-raise errors from public API methods.", + "isOptional": false, + "type": "bool" + }, + { + "name": "max_queue_size", + "description": "Maximum number of events buffered before upload.", + "isOptional": false, + "type": "int" + }, + { + "name": "send", + "description": "If False, queueing succeeds but events are not sent.", + "isOptional": false, + "type": "bool" + }, + { + "name": "on_error", + "description": "Optional callback invoked by background consumers when an upload fails. Keep it short and non-blocking. Calling lifecycle methods directly is safe and deferred, but do not start another thread or task that calls ``flush()``, ``join()``, or ``shutdown()`` and then wait for it from the callback.", + "isOptional": false, + "type": "any" + }, + { + "name": "flush_at", + "description": "Number of queued events that triggers a batch upload.", + "isOptional": false, + "type": "int" + }, + { + "name": "flush_interval", + "description": "Maximum seconds a background consumer waits before flushing a partial batch.", + "isOptional": false, + "type": "float" + }, + { + "name": "gzip", + "description": "Whether to gzip event upload payloads.", + "isOptional": false, + "type": "bool" + }, + { + "name": "max_retries", + "description": "Number of upload retries. Values below 0 are treated as 0.", + "isOptional": false, + "type": "int" + }, + { + "name": "sync_mode", + "description": "If True, send each event synchronously instead of using background worker threads. This blocks the calling thread; in asyncio applications such as FastAPI, use ``AsyncPosthog`` instead.", + "isOptional": false, + "type": "bool" + }, + { + "name": "timeout", + "description": "HTTP request timeout in seconds for event uploads.", + "isOptional": false, + "type": "float" + }, + { + "name": "thread", + "description": "Number of background consumer threads.", + "isOptional": false, + "type": "int" + }, + { + "name": "poll_interval", + "description": "Seconds between local feature flag definition refreshes.", + "isOptional": false, + "type": "int" + }, + { + "name": "personal_api_key", + "description": "Deprecated alias for ``secret_key``. Still honored for backwards compatibility; prefer ``secret_key``, which also accepts a Project Secret API Key.", + "isOptional": false, + "type": "any" + }, + { + "name": "disabled", + "description": "If True, disable captures and API requests. Useful in tests.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable server-side GeoIP enrichment. Defaults to True.", + "isOptional": false, + "type": "bool" + }, + { + "name": "is_server", + "description": "Whether events are emitted from a server-side runtime. Defaults to True; set to False when using the SDK as a client/CLI so the device OS is attributed to the person normally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "historical_migration", + "description": "Mark events as historical migration imports.", + "isOptional": false, + "type": "bool" + }, + { + "name": "feature_flags_request_timeout_seconds", + "description": "Timeout in seconds for feature flag and remote config requests.", + "isOptional": false, + "type": "float" + }, + { + "name": "feature_flags_request_max_retries", + "description": "Number of retries for feature flag requests after network, transport, or timeout failures. Defaults to 1. Set to 0 to disable retries.", + "isOptional": false, + "type": "int" + }, + { + "name": "super_properties", + "description": "Properties merged into every captured event.", + "isOptional": false, + "type": "any" + }, + { + "name": "enable_exception_autocapture", + "description": "Automatically capture uncaught exceptions.", + "isOptional": false, + "type": "bool" + }, + { + "name": "log_captured_exceptions", + "description": "Also log exceptions captured by error tracking.", + "isOptional": false, + "type": "bool" + }, + { + "name": "project_root", + "description": "Root path used to determine in-app stack frames for captured exceptions. Defaults to the current working directory.", + "isOptional": false, + "type": "any" + }, + { + "name": "privacy_mode", + "description": "For AI observability, capture usage metadata without prompt inputs or outputs.", + "isOptional": false, + "type": "bool" + }, + { + "name": "before_send", + "description": "Optional callback that can modify or drop events before upload. Return ``None`` to drop an event.", + "isOptional": false, + "type": "any" + }, + { + "name": "flag_fallback_cache_url", + "description": "Optional feature flag fallback cache URL, such as ``memory://local/?ttl=300&size=10000`` or a Redis URL.", + "isOptional": false, + "type": "any" + }, + { + "name": "enable_local_evaluation", + "description": "Whether to poll feature flag definitions for local evaluation when a personal API key is configured.", + "isOptional": false, + "type": "bool" + }, + { + "name": "flag_definition_cache_provider", + "description": "Optional external cache provider for sharing feature flag definitions across workers.", + "isOptional": true, + "type": "FlagDefinitionCacheProvider" + }, + { + "name": "capture_exception_code_variables", + "description": "Capture local variable values on exception stack frames.", + "isOptional": false, + "type": "bool" + }, + { + "name": "code_variables_mask_patterns", + "description": "Variable-name patterns to mask when capturing code variables.", + "isOptional": false, + "type": "any" + }, + { + "name": "code_variables_ignore_patterns", + "description": "Variable-name patterns to omit when capturing code variables.", + "isOptional": false, + "type": "any" + }, + { + "name": "code_variables_mask_url_credentials", + "description": "Scrub credentials embedded in URLs/DSNs (e.g. ``user:pass@host``) from captured code variables, regardless of the surrounding variable name. Defaults to True.", + "isOptional": false, + "type": "any" + }, + { + "name": "code_variables_detect_secrets", + "description": "Last-resort entropy-based detection that redacts high-entropy secret-looking values (API keys, tokens, strong passwords) sitting in innocuously-named variables, after the name and URL checks. Skips structured ids (UUIDs, ObjectIds, hashes). Defaults to True.", + "isOptional": false, + "type": "any" + }, + { + "name": "in_app_modules", + "description": "Module/package prefixes treated as in-app frames in captured exceptions.", + "isOptional": false, + "type": "UnionType[list[str], any]" + }, + { + "name": "enable_exception_autocapture_rate_limiting", + "description": "Rate limit autocaptured exceptions client-side with a token bucket per exception type. Disabled by default.", + "isOptional": false, + "type": "bool" + }, + { + "name": "exception_autocapture_bucket_size", + "description": "Maximum burst of autocaptured exceptions allowed per exception type (token bucket size, clamped to 0-100).", + "isOptional": false, + "type": "int" + }, + { + "name": "exception_autocapture_refill_rate", + "description": "Tokens restored per refill interval for each exception type's bucket.", + "isOptional": false, + "type": "int" + }, + { + "name": "exception_autocapture_refill_interval_seconds", + "description": "Seconds between token refills for autocaptured exception rate limiting.", + "isOptional": false, + "type": "int" + }, + { + "name": "capture_mode", + "description": "Capture wire protocol to use. Defaults to ``CaptureMode.V0`` (legacy ``/batch/``). Set ``CaptureMode.V1`` (or pass the string ``\"v1\"``) to opt into ``/i/v1/analytics/events``. When omitted, the ``POSTHOG_CAPTURE_MODE`` env var is consulted, then ``V0``.", + "isOptional": false, + "type": "CaptureMode" + }, + { + "name": "capture_compression", + "description": "Request-body compression for capture-v1 uploads (ignored in V0, which uses ``gzip``). ``CaptureCompression.GZIP`` or ``DEFLATE`` (or the strings ``\"gzip\"``/``\"deflate\"``). When omitted, the ``POSTHOG_CAPTURE_COMPRESSION`` env var is consulted, then the legacy ``gzip`` flag, then no compression.", + "isOptional": false, + "type": "CaptureCompression" + }, + { + "name": "secret_key", + "description": "A Personal API Key or Project Secret API Key, used to authenticate local feature flag evaluation, remote config payloads, and decrypted flag payloads. Example:: posthog.Client(project_api_key, secret_key=\"phx_...\")", + "isOptional": false, + "type": "any" + }, + { + "name": "metrics", + "description": "", + "isOptional": true, + "type": "dict" + }, + { + "name": "enable_full_ai_capture", + "description": "Route PostHog AI wrapper events through the dedicated AI capture endpoint and capture full AI content: skips string truncation and passes media (base64/data URIs) through unredacted. ``privacy_mode`` always wins. Defaults to False.", + "isOptional": false, + "type": "bool" + }, + { + "name": "capture_trace_context", + "description": "When OpenTelemetry is installed and a valid span is active at capture time, add its trace and span IDs as ``$trace_id`` and ``$span_id`` properties to events captured with ``capture()`` and ``capture_ai()``, so they can be correlated with backend traces. Explicit ``$trace_id``/``$span_id`` values passed in ``properties`` win. Exception events (``capture_exception``) always attach these IDs regardless of this setting. Defaults to False.", + "isOptional": false, + "type": "bool" + }, + { + "name": "_use_ai_lane", + "description": "", + "isOptional": false, + "type": "bool" + }, + { + "name": "_enable_multimodal_capture", + "description": "", + "isOptional": false, + "type": "bool" + }, + { + "name": "traces", + "description": "Config dict for distributed tracing: ``service_name``, ``service_version``, ``environment``, ``resource_attributes``, ``flush_interval`` (5 s), ``max_queue_size`` (2048), ``max_export_batch_size`` (512), ``max_live_spans`` (10000), ``max_span_age`` (3600 s), ``max_attributes_per_span`` (128), ``max_events_per_span`` (128), ``max_attribute_value_length`` (8192). ``before_span_send`` is a callable, or a list run in order, that receives each finished span as a dict (``trace_id``, ``span_id`` and ``parent_span_id`` are read-only) and returns it, edited, or ``None`` to drop it; a hook that raises drops the span. Tracing is off until this is provided. Spans export on a background timer even with ``sync_mode``; serverless handlers should call ``flush()`` before returning. Defaults to None.", + "isOptional": true, + "type": "dict" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import Posthog\n\nposthog = Posthog('', host='')" + } + ] + }, + { + "id": "alias", + "title": "alias", + "description": "Create an alias between two distinct IDs.", + "details": "", + "category": "Identification", + "params": [ + { + "name": "previous_id", + "description": "The previous distinct ID. Required - the call is dropped with a warning if it is missing or empty.", + "isOptional": true, + "type": "Number" + }, + { + "name": "distinct_id", + "description": "The new distinct ID to alias to. Falls back to the context distinct ID; the call is dropped with a warning if neither is available.", + "isOptional": true, + "type": "str" + }, + { + "name": "timestamp", + "description": "The timestamp of the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC.", + "isOptional": false, + "type": "datetime" + }, + { + "name": "uuid", + "description": "A unique identifier for the event. If provided, it must be a valid UUID string or uuid.UUID instance; invalid values are ignored and replaced with a newly generated UUID.", + "isOptional": true, + "type": "str" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this event.", + "isOptional": true, + "type": "bool" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.alias(previous_id='distinct_id', distinct_id='alias_id')" + } + ] + }, + { + "id": "capture", + "title": "capture", + "description": "Captures an event manually. [Learn about capture best practices](https://posthog.com/docs/product-analytics/capture-events)", + "details": "", + "category": "Capture", + "params": [ + { + "name": "event", + "description": "The event name to capture.", + "isOptional": true, + "type": "str" + }, + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalCaptureArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Anonymous event", + "code": "# Anonymous event\nposthog.capture('some-anon-event')" + }, + { + "id": "example_2", + "name": "Context usage", + "code": "# Context usage\nfrom posthog import identify_context, new_context\nwith new_context():\n identify_context('distinct_id_of_the_user')\n posthog.capture('user_signed_up')\n posthog.capture('user_logged_in')\n posthog.capture('some-custom-action', distinct_id='distinct_id_of_the_user')" + }, + { + "id": "example_3", + "name": "Set event properties", + "code": "# Set event properties\nposthog.capture(\n \"user_signed_up\",\n distinct_id=\"distinct_id_of_the_user\",\n properties={\n \"login_type\": \"email\",\n \"is_free_trial\": \"true\"\n }\n)" + }, + { + "id": "example_4", + "name": "Page view event", + "code": "# Page view event\nposthog.capture('$pageview', distinct_id=\"distinct_id_of_the_user\", properties={'$current_url': 'https://example.com'})" + } + ] + }, + { + "id": "capture_ai", + "title": "capture_ai", + "description": "Capture an AI event on the dedicated AI capture endpoint. Beta: the signature is stable; operational limits (per-event size cap, batching, endpoint) may change without notice. Takes the same arguments and returns the same value as `capture()`: the event UUID, or None when the event was not admitted (disabled client, or dropped by `before_send`). The event is queued on an isolated AI lane with its own consumer pool and a higher per-event size cap, posting to the dedicated AI ingestion endpoint. The payload is sent as given \u2014 no redaction or truncation is applied here.", + "details": "", + "category": "Capture", + "params": [ + { + "name": "event", + "description": "", + "isOptional": true, + "type": "str" + }, + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalCaptureArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + } + }, + { + "id": "capture_exception", + "title": "capture_exception", + "description": "Capture an exception for error tracking. When OpenTelemetry is installed and a valid span is active, its trace and span IDs are added as ``$trace_id`` and ``$span_id`` event properties.", + "details": "", + "category": "Error Tracking", + "params": [ + { + "name": "exception", + "description": "The exception to capture.", + "isOptional": true, + "type": "BaseException" + }, + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalCaptureArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "try:\n # Some code that might fail\n pass\nexcept Exception as e:\n posthog.capture_exception(e, 'user_distinct_id', properties=additional_properties)" + } + ] + }, + { + "id": "evaluate_flags", + "title": "evaluate_flags", + "description": "Evaluate all feature flags for a user in a single call and return a :class:`FeatureFlagEvaluations` snapshot. Branch on ``.is_enabled()`` / ``.get_flag()`` and pass the same snapshot to :meth:`capture` via the ``flags`` option so events carry the exact flag values the code branched on. Prefer this over repeated ``get_feature_flag()`` calls and over ``capture(send_feature_flags=True)`` \u2014 it consolidates flag evaluation into a single ``/flags`` request per incoming request. Local evaluation is transparent: when the poller resolves a flag, the snapshot's ``$feature_flag_called`` events are tagged ``locally_evaluated=True`` and reason ``\"Evaluated locally\"``.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The user's distinct ID. If ``None``, falls back to the context distinct_id. If still unresolvable, returns an empty snapshot.", + "isOptional": false, + "type": "Number" + }, + { + "name": "groups", + "description": "Mapping of group type to group key.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "Person properties to use for evaluation.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "Group properties keyed by group type.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "If True, never fall back to remote evaluation \u2014 flags that can't be evaluated locally are simply omitted from the snapshot.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup.", + "isOptional": true, + "type": "bool" + }, + { + "name": "flag_keys", + "description": "Optional list that scopes local evaluation, the underlying ``/flags`` request, and the returned snapshot. When omitted or ``None``, all flags are evaluated. An empty list returns an empty snapshot without evaluating flags. A requested key absent from loaded local definitions is included in one remote fallback per ``evaluate_flags`` call unless ``only_evaluate_locally`` is True. If the server also does not know the key, it is omitted from the snapshot.", + "isOptional": true, + "type": "list[str]" + }, + { + "name": "device_id", + "description": "Optional device ID override. If not provided, falls back to the context device_id (which may be set via tracing headers). Used by experience-continuity flags to match users across distinct_id changes.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "FeatureFlagEvaluations" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "flags = posthog.evaluate_flags(\n \"user_123\",\n person_properties={\"plan\": \"enterprise\"},\n)\nif flags.is_enabled(\"new-dashboard\"):\n render_new_dashboard()\nposthog.capture(\"page_viewed\", distinct_id=\"user_123\", flags=flags)" + } + ] + }, + { + "id": "feature_enabled", + "title": "feature_enabled", + "description": "Check if a feature flag is enabled for a user.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "The feature flag key.", + "isOptional": true, + "type": "str" + }, + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to only evaluate locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "send_feature_flag_events", + "description": "Whether to send feature flag events.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[bool]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "is_my_flag_enabled = posthog.feature_enabled('flag-key', 'distinct_id_of_your_user')\nif is_my_flag_enabled:\n # Do something differently for this user\n # Optional: fetch the payload\n matched_flag_payload = posthog.get_feature_flag_payload('flag-key', 'distinct_id_of_your_user')" + } + ] + }, + { + "id": "feature_flag_definitions", + "title": "feature_flag_definitions", + "description": "Return feature flag definitions loaded for local evaluation. Returns: The currently loaded feature flag definitions, or ``None`` before local evaluation has loaded definitions.", + "details": "", + "category": "Feature flags", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "flush", + "title": "flush", + "description": "Attempt immediate delivery of queued events and ended spans, keeping the client reusable. For a client shared across requests or serverless invocations (including the module-level client), call ``flush()`` before a short-lived invocation returns. Use ``shutdown()`` instead only for final process cleanup or when disposing of a client that will not be reused. Flush does not stop the background workers or feature flag poller, and does not end open spans. Returning does not guarantee server receipt: pending work can remain after a timeout or span export failure, and failed events may be dropped by the consumers. Calls made directly from SDK callbacks such as ``on_error`` are deferred rather than blocking the worker that invoked the callback.", + "details": "", + "category": null, + "params": [ + { + "name": "timeout_seconds", + "description": "Seconds to wait for event queues to drain, shared across event lanes. Defaults to 10 seconds. Pass ``None`` to wait without a time limit; this does not guarantee successful delivery. Queued spans flush concurrently. The span exporter waits up to this many seconds for an in-flight flush, then uses a fresh drain budget once it acquires the lock. If the lock wait times out, the in-flight flush is left to finish. At least one span request is attempted after acquiring the lock, even with no budget left; retriable failures are retried after backoff while budget remains, with one last attempt at the deadline. Each span request is bounded by the client's ``timeout`` in seconds, and the last request is not cut short. This is therefore not a strict wall-clock limit for the whole call. With ``None``, a retriable span failure is left for a later flush rather than retried within this call.", + "isOptional": true, + "type": "float" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.capture('event_name', distinct_id='user_id')\n# Before a serverless invocation returns; keep the shared client open.\nposthog.flush(timeout_seconds=10)" + } + ] + }, + { + "id": "get_active_span", + "title": "get_active_span", + "description": "The span that is active in the current context, or ``None``. Alpha. Only entering a span (``with posthog.start_span(...) as span:``) makes it active; a span started manually is not. Use it to propagate the trace to the next service: ``span.traceparent()`` is the header value.", + "details": "", + "category": "Tracing", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[Span]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "span = posthog.get_active_span()\nif span is not None:\n headers[\"traceparent\"] = span.traceparent()" + } + ] + }, + { + "id": "get_all_flags", + "title": "get_all_flags", + "description": "Get all feature flags for a user.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to only evaluate locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "flag_keys_to_evaluate", + "description": "A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance.", + "isOptional": true, + "type": "list[str]" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[dict[str, Union[bool, str]]]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.get_all_flags('distinct_id_of_your_user')" + } + ] + }, + { + "id": "get_all_flags_and_payloads", + "title": "get_all_flags_and_payloads", + "description": "Get all feature flags and their payloads for a user.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to only evaluate locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "flag_keys_to_evaluate", + "description": "A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance.", + "isOptional": true, + "type": "list[str]" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "FlagsAndPayloads" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.get_all_flags_and_payloads('distinct_id_of_your_user')" + } + ] + }, + { + "id": "get_feature_flag", + "title": "get_feature_flag", + "description": "Get multivariate feature flag value for a user.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "The feature flag key.", + "isOptional": true, + "type": "str" + }, + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to only evaluate locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "send_feature_flag_events", + "description": "Whether to send feature flag events.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Union[bool, str, any]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "enabled_variant = posthog.get_feature_flag('flag-key', 'distinct_id_of_your_user')\nif enabled_variant == 'variant-key': # replace 'variant-key' with the key of your variant\n # Do something differently for this user\n # Optional: fetch the payload\n matched_flag_payload = posthog.get_feature_flag_payload('flag-key', 'distinct_id_of_your_user')" + } + ] + }, + { + "id": "get_feature_flag_evaluation_runtime", + "title": "get_feature_flag_evaluation_runtime", + "description": "Return where a locally loaded feature flag is meant to be evaluated.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "The feature flag key.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[FeatureFlagEvaluationRuntime]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import FeatureFlagEvaluationRuntime\n\nruntime = posthog.get_feature_flag_evaluation_runtime(\"my-flag\")\nif runtime is FeatureFlagEvaluationRuntime.SERVER:\n ..." + } + ] + }, + { + "id": "get_feature_flag_keys_by_evaluation_runtime", + "title": "get_feature_flag_keys_by_evaluation_runtime", + "description": "Return the keys of locally loaded flags that a runtime can evaluate. A flag set to ``FeatureFlagEvaluationRuntime.ALL`` suits either runtime, so it is returned for ``CLIENT`` and for ``SERVER``, and asking for ``ALL`` returns every loaded flag. Use this to decide which flags to hand to a browser when a backend serves flags to its own frontend.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "evaluation_runtime", + "description": "The runtime to match, as a ``FeatureFlagEvaluationRuntime`` or its string value.", + "isOptional": true, + "type": "FeatureFlagEvaluationRuntime" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "list[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import FeatureFlagEvaluationRuntime\n\nclient_keys = posthog.get_feature_flag_keys_by_evaluation_runtime(\n FeatureFlagEvaluationRuntime.CLIENT\n)" + } + ] + }, + { + "id": "get_feature_flag_payload", + "title": "get_feature_flag_payload", + "description": "Get the payload for a feature flag.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "The feature flag key.", + "isOptional": true, + "type": "str" + }, + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "match_value", + "description": "The specific flag value to get payload for.", + "isOptional": false, + "type": "bool" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to only evaluate locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "send_feature_flag_events", + "description": "Deprecated. Use get_feature_flag() instead if you need events.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[object]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "is_my_flag_enabled = posthog.feature_enabled('flag-key', 'distinct_id_of_your_user')\n\nif is_my_flag_enabled:\n # Do something differently for this user\n # Optional: fetch the payload\n matched_flag_payload = posthog.get_feature_flag_payload('flag-key', 'distinct_id_of_your_user')" + } + ] + }, + { + "id": "get_feature_flag_result", + "title": "get_feature_flag_result", + "description": "Get a FeatureFlagResult object which contains the flag result and payload for a key by evaluating locally or remotely depending on whether local evaluation is enabled and the flag can be locally evaluated. This also captures the `$feature_flag_called` event unless `send_feature_flag_events` is `False`.", + "details": "", + "category": null, + "params": [ + { + "name": "key", + "description": "The feature flag key.", + "isOptional": true, + "type": "str" + }, + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to only evaluate locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "send_feature_flag_events", + "description": "Whether to send feature flag events.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[FeatureFlagResult]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "flag_result = posthog.get_feature_flag_result('flag-key', 'distinct_id_of_your_user')\nif flag_result and flag_result.get_value() == 'variant-key':\n # Do something differently for this user\n # Optional: fetch the payload\n matched_flag_payload = flag_result.payload" + } + ] + }, + { + "id": "get_feature_flags_and_payloads", + "title": "get_feature_flags_and_payloads", + "description": "Get feature flags and payloads for a user.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "flag_keys_to_evaluate", + "description": "A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance.", + "isOptional": true, + "type": "list[str]" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "FlagsAndPayloads" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "result = posthog.get_feature_flags_and_payloads('')" + } + ] + }, + { + "id": "get_feature_payloads", + "title": "get_feature_payloads", + "description": "Get feature flag payloads for a user, preserving valid serialized JSON.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "flag_keys_to_evaluate", + "description": "A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance.", + "isOptional": true, + "type": "list[str]" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "dict[str, Optional[str]]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "payloads = posthog.get_feature_payloads('')" + } + ] + }, + { + "id": "get_feature_variants", + "title": "get_feature_variants", + "description": "Get feature flag variants for a user.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "flag_keys_to_evaluate", + "description": "A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance.", + "isOptional": true, + "type": "list[str]" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "dict[str, Union[bool, str]]" + } + }, + { + "id": "get_flags_decision", + "title": "get_flags_decision", + "description": "Get feature flags decision.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": false, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "flag_keys_to_evaluate", + "description": "A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance.", + "isOptional": true, + "type": "list[str]" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "FlagsResponse" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "decision = posthog.get_flags_decision('user123')" + } + ] + }, + { + "id": "get_remote_config_payload", + "title": "get_remote_config_payload", + "description": "Get the payload for a remote config feature flag.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "The remote config feature flag key.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "get_tags", + "title": "get_tags", + "description": "Get all tags from the current context. Returns: Dict of all tags in the current context.", + "details": "", + "category": "Contexts", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "dict[str, Any]" + } + }, + { + "id": "group_identify", + "title": "group_identify", + "description": "Identify a group and set its properties.", + "details": "", + "category": "Identification", + "params": [ + { + "name": "group_type", + "description": "The type of group (e.g., 'company', 'team'). Required - the call is dropped with a warning if it is missing or empty.", + "isOptional": true, + "type": "str" + }, + { + "name": "group_key", + "description": "The unique identifier for the group. Required - the call is dropped with a warning if it is missing or empty.", + "isOptional": true, + "type": "str" + }, + { + "name": "properties", + "description": "A dictionary of properties to set on the group.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "timestamp", + "description": "The timestamp of the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC.", + "isOptional": false, + "type": "datetime" + }, + { + "name": "uuid", + "description": "A unique identifier for the event. If provided, it must be a valid UUID string or uuid.UUID instance; invalid values are ignored and replaced with a newly generated UUID.", + "isOptional": false, + "type": "str" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this event.", + "isOptional": true, + "type": "bool" + }, + { + "name": "distinct_id", + "description": "The distinct ID of the user performing the action.", + "isOptional": false, + "type": "Number" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.group_identify('company', 'company_id_in_your_db', {\n 'name': 'Awesome Inc.',\n 'employees': 11\n})" + } + ] + }, + { + "id": "identify_context", + "title": "identify_context", + "description": "Identify the current context with a distinct ID.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "distinct_id", + "description": "The distinct ID to associate with the current context and its children.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + } + }, + { + "id": "join", + "title": "join", + "description": "Attempt to process queued events and end the consumer threads. Do not use directly, call `shutdown()` instead. Failed or undrainable events may be dropped and reported through logging or ``on_error``; returning does not guarantee server receipt. Lifecycle cleanup is attempted once, and cleanup failures are logged without retry.", + "details": "", + "category": null, + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.join()" + } + ] + }, + { + "id": "load_feature_flags", + "title": "load_feature_flags", + "description": "Load feature flags for local evaluation.", + "details": "", + "category": "Feature flags", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.load_feature_flags()" + } + ] + }, + { + "id": "new_context", + "title": "new_context", + "description": "Create a new context for managing shared state. Learn more about [contexts](/docs/libraries/python#contexts).", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "fresh", + "description": "Whether to create a fresh context that doesn't inherit from parent.", + "isOptional": false, + "type": "bool" + }, + { + "name": "capture_exceptions", + "description": "Whether to automatically capture exceptions in this context. If omitted, defaults to this client's exception autocapture setting.", + "isOptional": true, + "type": "bool" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "with client.new_context():\n client.identify_context('')\n client.capture('event_name')" + } + ] + }, + { + "id": "scoped", + "title": "scoped", + "description": "Decorator that creates a new context for the wrapped function using this client.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "fresh", + "description": "Whether to create a fresh context that doesn't inherit from parent.", + "isOptional": false, + "type": "bool" + }, + { + "name": "capture_exceptions", + "description": "Whether to automatically capture exceptions in this context. If omitted, defaults to this client's exception autocapture setting.", + "isOptional": true, + "type": "bool" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "set", + "title": "set", + "description": "Set properties on a person profile.", + "details": "", + "category": "Identification", + "params": [ + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalSetArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Set with distinct id", + "code": "# Set with distinct id\nposthog.set(distinct_id='user123', properties={'name': 'Max Hedgehog'})" + } + ] + }, + { + "id": "set_context_device_id", + "title": "set_context_device_id", + "description": "Set the device ID for the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "device_id", + "description": "The device ID to associate with the current context and its children.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + } + }, + { + "id": "set_context_session", + "title": "set_context_session", + "description": "Set the session ID for the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "session_id", + "description": "The session ID to associate with the current context and its children.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + } + }, + { + "id": "set_once", + "title": "set_once", + "description": "Set properties on a person profile only if they haven't been set before.", + "details": "", + "category": "Identification", + "params": [ + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalSetArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.set_once(distinct_id='user123', properties={'initial_signup_date': '2024-01-01'})" + } + ] + }, + { + "id": "shutdown", + "title": "shutdown", + "description": "Attempt final delivery and permanently shut down the client. Call this for final process cleanup or when disposing of a client that will not be reused. It closes capture lanes, stops background workers and the feature flag poller, and tears down integrations. Do not call it at the end of each request or serverless invocation when the client is shared; call ``flush()`` before returning instead. A shut-down client cannot be reused; create a new client if needed. Normally this method blocks until queued events have been attempted and cleanup finishes. Failed or undrainable events may be dropped and reported through logging or ``on_error``; returning does not guarantee server receipt. Event draining has no timeout. Queued spans get one final flush with up to 30 seconds waiting for an in-flight flush, then a fresh 30-second drain budget; the last HTTP request is not cut short. Any spans it cannot send are discarded with a warning, as are spans still open. Lifecycle cleanup is attempted once, and cleanup failures are logged without retry. When called directly from an SDK callback such as ``on_error``, shutdown is deferred to avoid blocking the worker that invoked the callback. If the callback must coordinate a blocking shutdown, have it signal an application-owned thread and return before that thread calls shutdown. Do not wait inside the callback for another thread or task that calls a lifecycle method.", + "details": "", + "category": null, + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.shutdown()" + } + ] + }, + { + "id": "start_span", + "title": "start_span", + "description": "Start a span for distributed tracing. Alpha. Returns a span handle. Use it as a context manager to make it the active span for the block and end it on exit (recording a raised exception on the way out); or call ``end()`` yourself for a span that cannot wrap a block. Spans started inside the block nest under it automatically. Always returns a usable handle, even when tracing is off, so calling code never branches.", + "details": "", + "category": "Tracing", + "params": [ + { + "name": "name", + "description": "A low-cardinality operation name, e.g. ``GET /users/:id``. Variable values belong in attributes, not the name.", + "isOptional": true, + "type": "str" + }, + { + "name": "kind", + "description": "``internal`` (default), ``server``, ``client``, ``producer`` or ``consumer``.", + "isOptional": true, + "type": "str" + }, + { + "name": "attributes", + "description": "Initial attributes.", + "isOptional": true, + "type": "Mapping[str, Any]" + }, + { + "name": "parent", + "description": "A span handle, or an inbound W3C ``traceparent`` header value to continue a remote trace. Defaults to the active span. A forked child starts with no active span; pass the parent span to continue a trace across a fork.", + "isOptional": false, + "type": "Span" + }, + { + "name": "tracestate", + "description": "The inbound ``tracestate`` header accompanying a ``traceparent`` string ``parent``; preserved and propagated.", + "isOptional": true, + "type": "str" + }, + { + "name": "start_time", + "description": "A ``datetime`` or epoch seconds, to backdate the span.", + "isOptional": false, + "type": "datetime" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Span" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog = Posthog(\"\", traces={\"service_name\": \"checkout-api\"})\n\nwith posthog.start_span(\"POST /checkout\", parent=request.headers.get(\"traceparent\")) as span:\n span.set_attribute(\"plan\", user.plan)\n with posthog.start_span(\"db.query\", kind=\"client\"):\n ...\n outgoing_headers = {\"traceparent\": span.traceparent()}" + } + ] + }, + { + "id": "tag", + "title": "tag", + "description": "Add a tag to the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "name", + "description": "The tag key.", + "isOptional": true, + "type": "str" + }, + { + "name": "value", + "description": "The tag value.", + "isOptional": true, + "type": "Any" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + } + } + ] + }, + { + "id": "PostHogModule", + "title": "PostHog Module Functions", + "description": "Global functions available in the PostHog module", + "functions": [ + { + "id": "alias", + "title": "alias", + "description": "Associate user behaviour before and after they e.g. register, login, or perform some other identifying action.", + "details": "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?\". Particularly useful for associating user behaviour before and after they e.g. register, login, or perform some other identifying action.", + "category": "Identification", + "params": [ + { + "name": "previous_id", + "description": "The unique ID of the user before", + "isOptional": true, + "type": "Number" + }, + { + "name": "distinct_id", + "description": "The current unique id", + "isOptional": true, + "type": "str" + }, + { + "name": "timestamp", + "description": "Optional timestamp for the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC.", + "isOptional": false, + "type": "datetime" + }, + { + "name": "uuid", + "description": "Optional UUID for the event", + "isOptional": true, + "type": "str" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup", + "isOptional": true, + "type": "bool" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Alias user", + "code": "# Alias user\nfrom posthog import alias\nalias(previous_id='distinct_id', distinct_id='alias_id')" + } + ] + }, + { + "id": "capture", + "title": "capture", + "description": "Capture anything a user does within your system.", + "details": "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. A capture call requires an event name to specify the event. We recommend using [verb] [noun], like `movie played` or `movie updated` to easily identify what your events mean later on. Capture takes a number of optional arguments, which are defined by the `OptionalCaptureArgs` type.", + "category": "Events", + "params": [ + { + "name": "event", + "description": "The event name to specify the event **kwargs: Optional arguments including:", + "isOptional": true, + "type": "str" + }, + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalCaptureArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Context and capture usage", + "code": "# Context and capture usage\nfrom posthog import new_context, identify_context, tag_context, capture\n# Enter a new context (e.g. a request/response cycle, an instance of a background job, etc)\nwith new_context():\n # Associate this context with some user, by distinct_id\n identify_context('some user')\n\n # Capture an event, associated with the context-level distinct ID ('some user')\n capture('movie started')\n\n # Capture an event associated with some other user (overriding the context-level distinct ID)\n capture('movie joined', distinct_id='some-other-user')\n\n # Capture an event with some properties\n capture('movie played', properties={'movie_id': '123', 'category': 'romcom'})\n\n # Capture an event with some properties\n capture('purchase', properties={'product_id': '123', 'category': 'romcom'})\n # Capture an event with some associated group\n capture('purchase', groups={'company': 'id:5'})\n\n # Adding a tag to the current context will cause it to appear on all subsequent events\n tag_context('some-tag', 'some-value')\n\n capture('another-event') # Will be captured with `'some-tag': 'some-value'` in the properties dict" + }, + { + "id": "example_2", + "name": "Set event properties", + "code": "# Set event properties\nfrom posthog import capture\ncapture(\n \"user_signed_up\",\n distinct_id=\"distinct_id_of_the_user\",\n properties={\n \"login_type\": \"email\",\n \"is_free_trial\": \"true\"\n }\n)" + } + ] + }, + { + "id": "capture_ai", + "title": "capture_ai", + "description": "Capture an AI event on the dedicated AI capture endpoint. Beta: the signature is stable; operational limits (per-event size cap, batching, endpoint) may change without notice. Takes the same arguments and returns the same value as `capture()`: the event UUID, or None when the event was not admitted (disabled client, or dropped by `before_send`). The event is delivered on an isolated queue with its own consumer pool and a higher per-event size cap, posting to the dedicated AI ingestion endpoint. The payload is sent as given \u2014 no redaction or truncation is applied here.", + "details": "", + "category": "Events", + "params": [ + { + "name": "event", + "description": "The event name, normally one of the `$ai_*` event names. **kwargs: Same optional arguments as `capture()`.", + "isOptional": true, + "type": "str" + }, + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalCaptureArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import capture_ai\n\nuuid = capture_ai(\n \"$ai_generation\",\n distinct_id=\"user_123\",\n properties={\"$ai_model\": \"gpt-5\"},\n)" + } + ] + }, + { + "id": "capture_exception", + "title": "capture_exception", + "description": "Capture exceptions that happen in your code.", + "details": "Capture exception is idempotent - if it is called twice with the same exception instance, only a occurrence will be tracked in posthog. This is because, generally, contexts will cause exceptions to be captured automatically. However, to ensure you track an exception, if you catch and do not re-raise it, capturing it manually is recommended, unless you are certain it will have crossed a context boundary (e.g. by existing a `with posthog.new_context():` block already). If the passed exception was raised and caught, the captured stack trace will consist of every frame between where the exception was raised and the point at which it is captured (the \"traceback\"). If the passed exception was never raised, e.g. if you call `posthog.capture_exception(ValueError(\"Some Error\"))`, the stack trace captured will be the full stack trace at the moment the exception was captured. Note that heavy use of contexts will lead to truncated stack traces, as the exception will be captured by the context entered most recently, 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. `capture_exception` takes the same set of optional arguments as `capture`.", + "category": "Events", + "params": [ + { + "name": "exception", + "description": "The exception to capture. If not provided, the current exception is captured via `sys.exc_info()` **kwargs: Optional capture arguments including distinct_id, properties, timestamp, uuid, groups, flags, send_feature_flags, and disable_geoip. Overriding reserved exception properties through ``properties`` is deprecated and will stop working in the next major version.", + "isOptional": false, + "type": "BaseException" + }, + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalCaptureArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Capture exception", + "code": "# Capture exception\nfrom posthog import capture_exception\ntry:\n risky_operation()\nexcept Exception as e:\n capture_exception(e)" + } + ] + }, + { + "id": "evaluate_flags", + "title": "evaluate_flags", + "description": "Evaluate all feature flags for a user in a single call and return a :class:`FeatureFlagEvaluations` snapshot. Branch on ``.is_enabled()`` / ``.get_flag()`` and pass the same snapshot to ``capture()`` via the ``flags`` option so events carry the exact flag values the code branched on. Prefer this over repeated ``get_feature_flag()`` calls and over ``capture(send_feature_flags=True)`` \u2014 it consolidates flag evaluation into a single ``/flags`` request per incoming request.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The user's distinct ID. If ``None``, falls back to the context distinct_id. If still unresolvable, returns an empty snapshot.", + "isOptional": false, + "type": "Number" + }, + { + "name": "groups", + "description": "Mapping of group type to group key.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "Person properties to use for evaluation.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "Group properties keyed by group type.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "If ``True``, never fall back to remote evaluation and omit flags that cannot be evaluated locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup.", + "isOptional": true, + "type": "bool" + }, + { + "name": "flag_keys", + "description": "Optional list that scopes local evaluation, the underlying ``/flags`` request, and the returned snapshot. When omitted or ``None``, all flags are evaluated. An empty list returns an empty snapshot without evaluating flags. A requested key absent from loaded local definitions is included in one remote fallback per ``evaluate_flags`` call unless ``only_evaluate_locally`` is ``True``. If the server also does not know the key, it is omitted from the snapshot.", + "isOptional": true, + "type": "list[str]" + }, + { + "name": "device_id", + "description": "Optional device ID override. If not provided, falls back to the context device_id (which may be set via tracing headers). Used by experience-continuity flags to match users across distinct_id changes.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "FeatureFlagEvaluations" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import evaluate_flags, capture\nflags = evaluate_flags(\"user_123\", person_properties={\"plan\": \"enterprise\"})\nif flags.is_enabled(\"new-dashboard\"):\n render_new_dashboard()\ncapture(\"page_viewed\", distinct_id=\"user_123\", flags=flags)" + } + ] + }, + { + "id": "feature_enabled", + "title": "feature_enabled", + "description": "Use feature flags to enable or disable features for users.", + "details": "You can call `posthog.load_feature_flags()` before to make sure you're not doing unexpected requests.", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "The feature flag key", + "isOptional": true, + "type": "str" + }, + { + "name": "distinct_id", + "description": "The user's distinct ID", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "Groups mapping", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "Person properties", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "Group properties", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to evaluate only locally", + "isOptional": false, + "type": "bool" + }, + { + "name": "send_feature_flag_events", + "description": "Whether to send feature flag events", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "Optional device ID override for experience-continuity flags", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[bool]" + }, + "examples": [ + { + "id": "example_1", + "name": "Boolean feature flag", + "code": "# Boolean feature flag\nfrom posthog import feature_enabled, get_feature_flag_payload\nis_my_flag_enabled = feature_enabled('flag-key', 'distinct_id_of_your_user')\nif is_my_flag_enabled:\n matched_flag_payload = get_feature_flag_payload('flag-key', 'distinct_id_of_your_user')" + } + ] + }, + { + "id": "feature_flag_definitions", + "title": "feature_flag_definitions", + "description": "Returns loaded feature flags.", + "details": "Returns loaded feature flags, if any. Helpful for debugging what flag information you have loaded.", + "category": "Feature flags", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import feature_flag_definitions\ndefinitions = feature_flag_definitions()" + } + ] + }, + { + "id": "flush", + "title": "flush", + "description": "Attempt immediate delivery of queued events and ended spans, keeping the global client reusable. Call ``flush()`` before a short-lived request or serverless invocation returns when using the shared module-level client. Use ``shutdown()`` only for final process cleanup or a client that will not be reused. Flush does not stop the background workers or feature flag poller, and does not end open spans. Returning does not guarantee server receipt: pending work can remain after a timeout or span export failure, and failed events may be dropped by consumers. Calls made directly from SDK callbacks such as ``on_error`` are deferred rather than blocking the worker that invoked the callback.", + "details": "", + "category": "Client management", + "params": [ + { + "name": "timeout_seconds", + "description": "Seconds to wait for event queues to drain, shared across event lanes. Defaults to 10 seconds. Pass ``None`` to wait without a time limit; this does not guarantee successful delivery. Queued spans flush concurrently, waiting up to this many seconds for an in-flight flush before starting a fresh drain budget. A lock wait timeout leaves the in-flight flush to finish. At least one span request is attempted after acquiring the lock, even with no budget left; retriable failures are retried after backoff while budget remains, with one last attempt at the deadline. The last span request is not cut short and is bounded by the client's ``timeout`` in seconds, so this is not a strict wall-clock limit for the whole call. With ``None``, a retriable span failure is left for a later flush rather than retried within this call.", + "isOptional": true, + "type": "float" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "import posthog\nposthog.capture('event_name', distinct_id='user_id')\n# Before a serverless invocation returns; keep the global client open.\nposthog.flush(timeout_seconds=10)" + } + ] + }, + { + "id": "get_active_span", + "title": "get_active_span", + "description": "The span that is active in the current context, or ``None``. Alpha. Only entering a span (``with posthog.start_span(...) as span:``) makes it active; a span started manually is not. Use it to propagate the trace to the next service: ``span.traceparent()`` is the header value.", + "details": "", + "category": "Tracing", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[Span]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "span = posthog.get_active_span()\nif span is not None:\n headers[\"traceparent\"] = span.traceparent()" + } + ] + }, + { + "id": "get_all_flags", + "title": "get_all_flags", + "description": "Get all flags for a given user.", + "details": "Flags are key-value pairs where the key is the flag key and the value is the flag variant, or True, or False.", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The user's distinct ID", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "Groups mapping", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "Person properties", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "Group properties", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to evaluate only locally", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "Optional device ID override for experience-continuity flags", + "isOptional": true, + "type": "str" + }, + { + "name": "flag_keys_to_evaluate", + "description": "Optional list of flag keys to evaluate (evaluates all if None)", + "isOptional": true, + "type": "list[str]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[dict[str, Union[bool, str]]]" + }, + "examples": [ + { + "id": "example_1", + "name": "All flags for user", + "code": "# All flags for user\nfrom posthog import get_all_flags\nget_all_flags('distinct_id_of_your_user')" + } + ] + }, + { + "id": "get_all_flags_and_payloads", + "title": "get_all_flags_and_payloads", + "description": "Get all feature flag values and payloads for a user.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The user's distinct ID.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "Mapping of group type to group key.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "Person properties to use for evaluation.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "Group properties keyed by group type.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to evaluate only locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup.", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "Optional device ID override for experience-continuity flags.", + "isOptional": true, + "type": "str" + }, + { + "name": "flag_keys_to_evaluate", + "description": "Optional list of flag keys to evaluate. Evaluates all flags when omitted.", + "isOptional": true, + "type": "list[str]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "FlagsAndPayloads" + } + }, + { + "id": "get_feature_flag", + "title": "get_feature_flag", + "description": "Get feature flag variant for users. Used with experiments.", + "details": "`groups` are a mapping from group type to group key. So, if you have a group type of \"organization\" and a group key of \"5\", you would pass groups={\"organization\": \"5\"}. `group_properties` take the format: { group_type_name: { group_properties } }. So, for example, if you have the group type \"organization\" and the group key \"5\", with the properties name, and employee count, you'll send these as: group_properties={\"organization\": {\"name\": \"PostHog\", \"employees\": 11}}.", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "The feature flag key", + "isOptional": true, + "type": "str" + }, + { + "name": "distinct_id", + "description": "The user's distinct ID", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "Groups mapping from group type to group key", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "Person properties", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "Group properties in format { group_type_name: { group_properties } }", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to evaluate only locally", + "isOptional": false, + "type": "bool" + }, + { + "name": "send_feature_flag_events", + "description": "Whether to send feature flag events", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "Optional device ID override for experience-continuity flags", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Union[bool, str, any]" + }, + "examples": [ + { + "id": "example_1", + "name": "Multivariate feature flag", + "code": "# Multivariate feature flag\nfrom posthog import get_feature_flag, get_feature_flag_payload\nenabled_variant = get_feature_flag('flag-key', 'distinct_id_of_your_user')\nif enabled_variant == 'variant-key':\n matched_flag_payload = get_feature_flag_payload('flag-key', 'distinct_id_of_your_user')" + } + ] + }, + { + "id": "get_feature_flag_evaluation_runtime", + "title": "get_feature_flag_evaluation_runtime", + "description": "Return where a locally loaded feature flag is meant to be evaluated.", + "details": "Reads the `evaluation_runtime` each flag definition carries, so no extra request is made. Returns `None` when local evaluation has not loaded a definition for this key. A definition that carries no runtime reports `FeatureFlagEvaluationRuntime.ALL`, the default PostHog applies.", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[FeatureFlagEvaluationRuntime]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import FeatureFlagEvaluationRuntime, get_feature_flag_evaluation_runtime\nruntime = get_feature_flag_evaluation_runtime(\"my-flag\")" + } + ] + }, + { + "id": "get_feature_flag_keys_by_evaluation_runtime", + "title": "get_feature_flag_keys_by_evaluation_runtime", + "description": "Return the keys of locally loaded flags that a runtime can evaluate.", + "details": "A flag set to `FeatureFlagEvaluationRuntime.ALL` suits either runtime, so it is returned for `CLIENT` and for `SERVER`. Use this to decide which flags to hand to a browser when a backend serves flags to its own frontend.", + "category": "Feature flags", + "params": [ + { + "name": "evaluation_runtime", + "description": "", + "isOptional": true, + "type": "FeatureFlagEvaluationRuntime" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "list[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import FeatureFlagEvaluationRuntime, get_feature_flag_keys_by_evaluation_runtime\nclient_keys = get_feature_flag_keys_by_evaluation_runtime(FeatureFlagEvaluationRuntime.CLIENT)" + } + ] + }, + { + "id": "get_feature_flag_payload", + "title": "get_feature_flag_payload", + "description": "Get the payload associated with a feature flag value. Deprecated for new code. Prefer ``evaluate_flags()`` and ``flags.get_flag_payload(key)`` so flag evaluation happens once per request.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "The feature flag key.", + "isOptional": true, + "type": "str" + }, + { + "name": "distinct_id", + "description": "The user's distinct ID.", + "isOptional": true, + "type": "Number" + }, + { + "name": "match_value", + "description": "Optional flag value to use when selecting a payload.", + "isOptional": false, + "type": "bool" + }, + { + "name": "groups", + "description": "Mapping of group type to group key.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "Person properties to use for evaluation.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "Group properties keyed by group type.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to evaluate only locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "send_feature_flag_events", + "description": "Whether to send a $feature_flag_called event.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup.", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "Optional device ID override for experience-continuity flags.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[object]" + } + }, + { + "id": "get_feature_flag_result", + "title": "get_feature_flag_result", + "description": "Get a FeatureFlagResult object which contains the flag result and payload. This method evaluates a feature flag and returns a FeatureFlagResult object containing: - enabled: Whether the flag is enabled - variant: The variant value if the flag has variants - payload: The payload associated with the flag (automatically deserialized from JSON) - key: The flag key - reason: Why the flag was enabled/disabled", + "details": "", + "category": null, + "params": [ + { + "name": "key", + "description": "The feature flag key.", + "isOptional": true, + "type": "str" + }, + { + "name": "distinct_id", + "description": "The user's distinct ID.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "Mapping of group type to group key.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "Person properties to use for evaluation.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "Group properties keyed by group type.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to evaluate only locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "send_feature_flag_events", + "description": "Whether to send a $feature_flag_called event.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup.", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "Optional device ID override for experience-continuity flags.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[FeatureFlagResult]" + } + }, + { + "id": "get_remote_config_payload", + "title": "get_remote_config_payload", + "description": "Get the payload for a remote config feature flag.", + "details": "", + "category": null, + "params": [ + { + "name": "key", + "description": "The key of the feature flag", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "get_tags", + "title": "get_tags", + "description": "Get all tags from the current context. Returns: Dict of all tags in the current context", + "details": "", + "category": "Contexts", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "dict[str, Any]" + } + }, + { + "id": "group_identify", + "title": "group_identify", + "description": "Set properties on a group.", + "details": "", + "category": "Identification", + "params": [ + { + "name": "group_type", + "description": "Type of your group. Required - the call is dropped with a warning if it is missing or empty.", + "isOptional": true, + "type": "str" + }, + { + "name": "group_key", + "description": "Unique identifier of the group. Required - the call is dropped with a warning if it is missing or empty.", + "isOptional": true, + "type": "str" + }, + { + "name": "properties", + "description": "Properties to set on the group", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "timestamp", + "description": "Optional timestamp for the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC.", + "isOptional": false, + "type": "datetime" + }, + { + "name": "uuid", + "description": "Optional UUID for the event", + "isOptional": true, + "type": "str" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup", + "isOptional": true, + "type": "bool" + }, + { + "name": "distinct_id", + "description": "Optional distinct ID of the user performing the action", + "isOptional": false, + "type": "Number" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Group identify", + "code": "# Group identify\nfrom posthog import group_identify\ngroup_identify('company', 'company_id_in_your_db', {\n 'name': 'Awesome Inc.',\n 'employees': 11\n})" + } + ] + }, + { + "id": "identify_context", + "title": "identify_context", + "description": "Identify the current context with a distinct ID.", + "details": "", + "category": "Identification", + "params": [ + { + "name": "distinct_id", + "description": "The distinct ID to associate with the current context and its children", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import identify_context\nidentify_context(\"user_123\")" + } + ] + }, + { + "id": "join", + "title": "join", + "description": "Attempt to process queued events and stop the client's background workers. Use `shutdown()` directly in most cases. Failed or undrainable events may be dropped and reported through logging or ``on_error``; returning does not guarantee server receipt. Lifecycle cleanup is attempted once, and cleanup failures are logged without retry.", + "details": "", + "category": "Client management", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import join\njoin()" + } + ] + }, + { + "id": "load_feature_flags", + "title": "load_feature_flags", + "description": "Load feature flag definitions from PostHog.", + "details": "", + "category": "Feature flags", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import load_feature_flags\nload_feature_flags()" + } + ] + }, + { + "id": "new_context", + "title": "new_context", + "description": "Create a new context scope that will be active for the duration of the with block.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "fresh", + "description": "Whether to start with a fresh context (default: False)", + "isOptional": false, + "type": "bool" + }, + { + "name": "capture_exceptions", + "description": "Whether to capture exceptions raised within the context. If omitted, defaults to the relevant client's exception autocapture setting.", + "isOptional": true, + "type": "bool" + }, + { + "name": "client", + "description": "Optional Posthog client instance to use for this context (default: None)", + "isOptional": true, + "type": "Client" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import new_context, tag, capture\nwith new_context():\n tag(\"request_id\", \"123\")\n capture(\"event_name\", properties={\"property\": \"value\"})" + } + ] + }, + { + "id": "scoped", + "title": "scoped", + "description": "Decorator that creates a new context for the function.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "fresh", + "description": "Whether to start with a fresh context (default: False)", + "isOptional": false, + "type": "bool" + }, + { + "name": "capture_exceptions", + "description": "Whether to capture and track exceptions with posthog error tracking. If omitted, defaults to the global exception autocapture setting.", + "isOptional": true, + "type": "bool" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import scoped, tag, capture\n@scoped()\ndef process_payment(payment_id):\n tag(\"payment_id\", payment_id)\n capture(\"payment_started\")" + } + ] + }, + { + "id": "set", + "title": "set", + "description": "Set properties on a user record.", + "details": "This will overwrite previous people property values. Generally operates similar to `capture`, with distinct_id being an optional argument, defaulting to the current context's distinct ID. If there is no context-level distinct ID, and no override distinct_id is passed, this function will do nothing. Context tags are folded into $set properties, so tagging the current context and then calling `set` will cause those tags to be set on the user (unlike capture, which causes them to just be set on the event).", + "category": "Identification", + "params": [ + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalSetArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Set person properties", + "code": "# Set person properties\nfrom posthog import set\nset(distinct_id='distinct_id', properties={'name': 'Max Hedgehog'})" + } + ] + }, + { + "id": "set_capture_exception_code_variables_context", + "title": "set_capture_exception_code_variables_context", + "description": "Override code-variable capture for exceptions in the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "enabled", + "description": "Whether exceptions captured in this context should include local variable values from stack frames.", + "isOptional": true, + "type": "bool" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "set_code_variables_detect_secrets_context", + "title": "set_code_variables_detect_secrets_context", + "description": "Whether to apply entropy-based secret detection as a last-resort redaction of high-entropy values (API keys, tokens, strong passwords) in captured code variables for the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "enabled", + "description": "", + "isOptional": true, + "type": "bool" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "set_code_variables_ignore_patterns_context", + "title": "set_code_variables_ignore_patterns_context", + "description": "Override code-variable ignore patterns for exceptions in the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "ignore_patterns", + "description": "Variable-name patterns that should be omitted entirely when code variables are captured.", + "isOptional": true, + "type": "list[str]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "set_code_variables_mask_patterns_context", + "title": "set_code_variables_mask_patterns_context", + "description": "Override code-variable mask patterns for exceptions in the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "mask_patterns", + "description": "Variable-name patterns whose values should be replaced with ``***`` when code variables are captured.", + "isOptional": true, + "type": "list[str]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "set_code_variables_mask_url_credentials_context", + "title": "set_code_variables_mask_url_credentials_context", + "description": "Whether to scrub credentials embedded in URLs/DSNs (e.g. user:pass@host) from captured code variables for the current context.", + "details": "", + "category": null, + "params": [ + { + "name": "enabled", + "description": "", + "isOptional": true, + "type": "bool" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "set_context_device_id", + "title": "set_context_device_id", + "description": "Set the device ID for the current context, associating all feature flag requests in this or child contexts with the given device ID.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "device_id", + "description": "The device ID to associate with the current context and its children", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import set_context_device_id\nset_context_device_id(\"device_123\")" + } + ] + }, + { + "id": "set_context_session", + "title": "set_context_session", + "description": "Set the session ID for the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "session_id", + "description": "The session ID to associate with the current context and its children", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import set_context_session\nset_context_session(\"session_123\")" + } + ] + }, + { + "id": "set_once", + "title": "set_once", + "description": "Set properties on a user record, only if they do not yet exist.", + "details": "This will not overwrite previous people property values, unlike `set`. Otherwise, operates in an identical manner to `set`.", + "category": "Identification", + "params": [ + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalSetArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Set property once", + "code": "# Set property once\nfrom posthog import set_once\nset_once(distinct_id='distinct_id', properties={'initial_url': '/blog'})" + } + ] + }, + { + "id": "setup", + "title": "setup", + "description": "Create or return the global PostHog client configured by module settings. Most applications should either instantiate ``Posthog`` directly or set ``posthog.api_key``/other module settings before calling top-level helpers. ``setup()`` is called automatically by global APIs such as ``capture()``. Returns: The global ``Client`` instance. If both ``api_key`` and ``project_api_key`` are missing or blank, the client is disabled and module-level calls become no-ops.", + "details": "", + "category": "Initialization", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Client" + } + }, + { + "id": "shutdown", + "title": "shutdown", + "description": "Attempt final delivery and permanently shut down the global client. Call this for final process cleanup, not at the end of each request or serverless invocation that shares the module-level client. Use ``flush()`` before returning from those invocations instead. Shutdown closes capture lanes, stops background workers and the feature flag poller, and tears down integrations. The global client is not recreated automatically after shutdown. This normally blocks until queued events have been attempted and cleanup finishes. Failed or undrainable events may be dropped and reported through logging or ``on_error``; returning does not guarantee server receipt. Event draining has no timeout. Queued spans get a final flush with up to 30 seconds waiting for an in-flight flush, then a fresh 30-second drain budget; the last HTTP request is not cut short. Unsent queued spans and still-open spans are discarded with a warning. Lifecycle cleanup is attempted once, and cleanup failures are logged without retry. Calls made directly from SDK callbacks such as ``on_error`` are deferred to avoid deadlocking the worker. If blocking completion is required, signal an application-owned thread, return from the callback, and call ``shutdown()`` from that thread. Do not wait inside a callback for another thread or task calling a lifecycle method.", + "details": "", + "category": "Client management", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import shutdown\nshutdown()" + } + ] + }, + { + "id": "start_span", + "title": "start_span", + "description": "Start a span for distributed tracing. Alpha. Returns a span handle. Use it as a context manager to make it the active span for the block and end it on exit (recording a raised exception on the way out); or call ``end()`` yourself for a span that cannot wrap a block. Spans started inside the block nest under it automatically. Always returns a usable handle, even when tracing is off, so calling code never branches.", + "details": "", + "category": "Tracing", + "params": [ + { + "name": "name", + "description": "A low-cardinality operation name, e.g. ``GET /users/:id``. Variable values belong in attributes, not the name.", + "isOptional": true, + "type": "str" + }, + { + "name": "kind", + "description": "``internal`` (default), ``server``, ``client``, ``producer`` or ``consumer``.", + "isOptional": true, + "type": "str" + }, + { + "name": "attributes", + "description": "Initial attributes.", + "isOptional": true, + "type": "Mapping[str, Any]" + }, + { + "name": "parent", + "description": "A span handle, or an inbound W3C ``traceparent`` header value to continue a remote trace. Defaults to the active span.", + "isOptional": false, + "type": "Span" + }, + { + "name": "tracestate", + "description": "The inbound ``tracestate`` header accompanying a ``traceparent`` string ``parent``; preserved and propagated.", + "isOptional": true, + "type": "str" + }, + { + "name": "start_time", + "description": "A ``datetime`` or epoch seconds, to backdate the span.", + "isOptional": false, + "type": "datetime" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Span" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "import posthog\nposthog.traces = {\"service_name\": \"checkout-api\"}\n\nwith posthog.start_span(\"POST /checkout\", parent=request.headers.get(\"traceparent\")) as span:\n span.set_attribute(\"plan\", user.plan)\n with posthog.start_span(\"db.query\", kind=\"client\"):\n ...\n outgoing_headers = {\"traceparent\": span.traceparent()}" + } + ] + }, + { + "id": "tag", + "title": "tag", + "description": "Add a tag to the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "name", + "description": "The tag key", + "isOptional": true, + "type": "str" + }, + { + "name": "value", + "description": "The tag value", + "isOptional": true, + "type": "Any" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import tag\ntag(\"user_id\", \"123\")" + } + ] + } + ] + } + ], + "categories": [ + "Initialization", + "Identification", + "Capture", + "Error Tracking", + "Feature flags", + "Tracing", + "Contexts", + "Events", + "Client management" + ] +} \ No newline at end of file diff --git a/references/posthog-python-references-latest.json b/references/posthog-python-references-latest.json index 226ca01c..00e8b9d0 100644 --- a/references/posthog-python-references-latest.json +++ b/references/posthog-python-references-latest.json @@ -2,7 +2,7 @@ "id": "posthog-python", "hogRef": "0.3", "info": { - "version": "7.75.10", + "version": "7.75.11", "id": "posthog-python", "title": "PostHog Python SDK", "description": "Integrate PostHog into any python application.", From 10c8a0224b920031e774d7b43a5f4eb704a77181 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 23 Oct 2026 12:24:08 -0400 Subject: [PATCH 19/60] chore(deps): bump opentelemetry-api from 1.40.0 to 1.45.1 (#1117) Bumps [opentelemetry-api](https://github.com/open-telemetry/opentelemetry-python) from 1.40.0 to 1.45.1. - [Release notes](https://github.com/open-telemetry/opentelemetry-python/releases) - [Changelog](https://github.com/open-telemetry/opentelemetry-python/blob/main/CHANGELOG.md) - [Commits](https://github.com/open-telemetry/opentelemetry-python/compare/v1.40.0...v1.45.1) --- updated-dependencies: - dependency-name: opentelemetry-api dependency-version: 1.45.1 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> (cherry picked from commit bc7b40331a4a9bf18456f6e9136147ab7118ca0f) --- uv.lock | 80 +++++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 55 insertions(+), 25 deletions(-) diff --git a/uv.lock b/uv.lock index fe475d40..7663437b 100644 --- a/uv.lock +++ b/uv.lock @@ -2691,50 +2691,80 @@ wheels = [ [[package]] name = "opentelemetry-api" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "importlib-metadata" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2c/1d/4049a9e8698361cc1a1aa03a6c59e4fa4c71e0c0f94a30f988a6876a2ae6/opentelemetry_api-1.40.0.tar.gz", hash = "sha256:159be641c0b04d11e9ecd576906462773eb97ae1b657730f0ecf64d32071569f", size = 70851, upload-time = "2026-03-04T14:17:21.555Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2e/02/6e0ae9cc61bd3169d401077b507b3ebc344745171e1051ab430be012dcd9/opentelemetry_api-1.45.1.tar.gz", hash = "sha256:aa38ed19bcc084ba42782a73255b3582283eced7ad6dddbd6695189e69adfb75", size = 72804, upload-time = "2026-10-06T17:32:58.133Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5f/bf/93795954016c522008da367da292adceed71cca6ee1717e1d64c83089099/opentelemetry_api-1.40.0-py3-none-any.whl", hash = "sha256:82dd69331ae74b06f6a874704be0cfaa49a1650e1537d4a813b86ecef7d0ecf9", size = 68676, upload-time = "2026-03-04T14:17:01.24Z" }, + { url = "https://files.pythonhosted.org/packages/1e/41/f7dcf80b81ee8e71c1a2b59f14208bc723edbd89ed027a73b175abf6348e/opentelemetry_api-1.45.1-py3-none-any.whl", hash = "sha256:b31553efa588ae44bc306f863c785c5333a9ecc091248c6ee68b4b6c87fdedfb", size = 60256, upload-time = "2026-10-06T17:32:33.506Z" }, +] + +[[package]] +name = "opentelemetry-exporter-http-transport" +version = "0.66b1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/62/0c/e3ebdb4b507f66afcc905e6885a4946969bd75b45988492643356fbbdc63/opentelemetry_exporter_http_transport-0.66b1.tar.gz", hash = "sha256:443080203bf52586ce0b2ad901e8951c61833eab1aa539ae6f1f16fe9e8e7952", size = 11693, upload-time = "2026-10-06T17:32:59.65Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/69/6af86ff66492b481c6a4c05dcfd68beb47ed8ba046440a26a2aac76b95c7/opentelemetry_exporter_http_transport-0.66b1-py3-none-any.whl", hash = "sha256:2f95404bdee7f9d2d529c7de56c7bd86d014d774d8fbf137810e0167f8a492bf", size = 12155, upload-time = "2026-10-06T17:32:35.454Z" }, +] + +[package.optional-dependencies] +requests = [ + { name = "requests" }, +] + +[[package]] +name = "opentelemetry-exporter-otlp-common" +version = "0.66b1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-sdk" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cb/19/41de712173f43057e4532d42ece7d0c6d4210d353e5752433cb14987643f/opentelemetry_exporter_otlp_common-0.66b1.tar.gz", hash = "sha256:6b1403487a2185ac1feb45fd5546fdf8630ce71c36bcefaadf51e2130e9e23f9", size = 14325, upload-time = "2026-10-06T17:33:01.725Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fc/39/8c23d67665c762aa51840fa06f86e902e8f6f1693bc8d7e3d98cd6e2f753/opentelemetry_exporter_otlp_common-0.66b1-py3-none-any.whl", hash = "sha256:00ff8592c3a7cb729ff3fdc7ffa12372c243bdf2163e80c180994d0c7bd83ee9", size = 12385, upload-time = "2026-10-06T17:32:38.177Z" }, ] [[package]] name = "opentelemetry-exporter-otlp-proto-common" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-proto" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/51/bc/1559d46557fe6eca0b46c88d4c2676285f1f3be2e8d06bb5d15fbffc814a/opentelemetry_exporter_otlp_proto_common-1.40.0.tar.gz", hash = "sha256:1cbee86a4064790b362a86601ee7934f368b81cd4cc2f2e163902a6e7818a0fa", size = 20416, upload-time = "2026-03-04T14:17:23.801Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c1/8e/65e85e5137991a3c493b11682151d198638a5bc1dd4b4c5f67e013c57d7c/opentelemetry_exporter_otlp_proto_common-1.45.1.tar.gz", hash = "sha256:2e4adcc3a67bcf57804fc49514f0ef64974ca7590aa3491da389852b4a0628f6", size = 18873, upload-time = "2026-10-06T17:33:04.471Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8b/ca/8f122055c97a932311a3f640273f084e738008933503d0c2563cd5d591fc/opentelemetry_exporter_otlp_proto_common-1.40.0-py3-none-any.whl", hash = "sha256:7081ff453835a82417bf38dccf122c827c3cbc94f2079b03bba02a3165f25149", size = 18369, upload-time = "2026-03-04T14:17:04.796Z" }, + { url = "https://files.pythonhosted.org/packages/84/aa/92f225d353904e7f70b8b3e3c1b02db0cf56f744c2e83c581dc372e78873/opentelemetry_exporter_otlp_proto_common-1.45.1-py3-none-any.whl", hash = "sha256:2f446183ae7047b036226f1d846c41a834b0e8755ad13b51a51dd38952eb466c", size = 15393, upload-time = "2026-10-06T17:32:41.911Z" }, ] [[package]] name = "opentelemetry-exporter-otlp-proto-http" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "googleapis-common-protos" }, { name = "opentelemetry-api" }, + { name = "opentelemetry-exporter-http-transport", extra = ["requests"] }, + { name = "opentelemetry-exporter-otlp-common" }, { name = "opentelemetry-exporter-otlp-proto-common" }, { name = "opentelemetry-proto" }, { name = "opentelemetry-sdk" }, { name = "requests" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2e/fa/73d50e2c15c56be4d000c98e24221d494674b0cc95524e2a8cb3856d95a4/opentelemetry_exporter_otlp_proto_http-1.40.0.tar.gz", hash = "sha256:db48f5e0f33217588bbc00274a31517ba830da576e59503507c839b38fa0869c", size = 17772, upload-time = "2026-03-04T14:17:25.324Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/17/26487707ea4caa97b17e6e4b5fa72133a53512ffa2f5cf7a49ef284b29cb/opentelemetry_exporter_otlp_proto_http-1.45.1.tar.gz", hash = "sha256:45c218405ce3fd879596924b1874bf9a8f6880206d61065c5a912c8e5c297fb7", size = 28839, upload-time = "2026-10-06T17:33:05.713Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/3a/8865d6754e61c9fb170cdd530a124a53769ee5f740236064816eb0ca7301/opentelemetry_exporter_otlp_proto_http-1.40.0-py3-none-any.whl", hash = "sha256:a8d1dab28f504c5d96577d6509f80a8150e44e8f45f82cdbe0e34c99ab040069", size = 19960, upload-time = "2026-03-04T14:17:07.153Z" }, + { url = "https://files.pythonhosted.org/packages/aa/1f/517eaa0187ba106a9da97160ce2add3a371812681dc440930b267f714e42/opentelemetry_exporter_otlp_proto_http-1.45.1-py3-none-any.whl", hash = "sha256:24a97cf3753c7fb52fad44a696e452ff371686339e2acf3309e2eda3d0230700", size = 22180, upload-time = "2026-10-06T17:32:43.946Z" }, ] [[package]] name = "opentelemetry-instrumentation" -version = "0.61b0" +version = "0.66b1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, @@ -2742,62 +2772,62 @@ dependencies = [ { name = "packaging" }, { name = "wrapt" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/da/37/6bf8e66bfcee5d3c6515b79cb2ee9ad05fe573c20f7ceb288d0e7eeec28c/opentelemetry_instrumentation-0.61b0.tar.gz", hash = "sha256:cb21b48db738c9de196eba6b805b4ff9de3b7f187e4bbf9a466fa170514f1fc7", size = 32606, upload-time = "2026-03-04T14:20:16.825Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a5/03/89e47ff8d52a4f83b343e6eb9ef1698ff45357216e5b6b2b21e0da5c5c7d/opentelemetry_instrumentation-0.66b1.tar.gz", hash = "sha256:e79a510f7d87c72d95e964ddb42193a0d9a75668c027d980eab032ea1322a5ce", size = 43308, upload-time = "2026-10-06T17:36:10.703Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d8/3e/f6f10f178b6316de67f0dfdbbb699a24fbe8917cf1743c1595fb9dcdd461/opentelemetry_instrumentation-0.61b0-py3-none-any.whl", hash = "sha256:92a93a280e69788e8f88391247cc530fd81f16f2b011979d4d6398f805cfbc63", size = 33448, upload-time = "2026-03-04T14:19:02.447Z" }, + { url = "https://files.pythonhosted.org/packages/da/b2/d1413681ff43e13ac9860df27e1226d3199ab0b97b352ceea41abcc660a5/opentelemetry_instrumentation-0.66b1-py3-none-any.whl", hash = "sha256:4c4aa14dc9a24a02325a9d4c42c4d0208dbb1374c2b1b8fe6c9392d59f3e1008", size = 36904, upload-time = "2026-10-06T17:35:11.663Z" }, ] [[package]] name = "opentelemetry-instrumentation-celery" -version = "0.61b0" +version = "0.66b1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, { name = "opentelemetry-instrumentation" }, { name = "opentelemetry-semantic-conventions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8d/43/e79108a804d16b1dc8ff28edd0e94ac393cf6359a5adcd7cdd2ec4be85f4/opentelemetry_instrumentation_celery-0.61b0.tar.gz", hash = "sha256:0e352a567dc89ed8bc083fc635035ce3c5b96bbbd92831ffd676e93b87f8e94f", size = 14780, upload-time = "2026-03-04T14:20:27.776Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d5/66/c2bbfbb7c90850dc52dee5d4968ab45a986093d2b77a631ff17e4107eefc/opentelemetry_instrumentation_celery-0.66b1.tar.gz", hash = "sha256:3b6c5539c8d4a060edbc0eb1f334ef909d0f9a5ecb7af545bdd73644e6e2e814", size = 15947, upload-time = "2026-10-06T17:36:20.175Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a2/ed/c05f3c84b455654eb6c047474ffde61ed92efc24030f64213c98bca9d44b/opentelemetry_instrumentation_celery-0.61b0-py3-none-any.whl", hash = "sha256:01235733ff0cdf571cb03b270645abb14b9c8d830313dc5842097ec90146320b", size = 13856, upload-time = "2026-03-04T14:19:20.98Z" }, + { url = "https://files.pythonhosted.org/packages/9e/73/b7597b2fede14cafe7122d1028bd6f648d9c1e049a61ab36481a4ed4f0df/opentelemetry_instrumentation_celery-0.66b1-py3-none-any.whl", hash = "sha256:145b5eece41331141edc8f454984238b05b107d7ead928b49e7a1916d5e48e99", size = 13480, upload-time = "2026-10-06T17:35:25.828Z" }, ] [[package]] name = "opentelemetry-proto" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "protobuf" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4c/77/dd38991db037fdfce45849491cb61de5ab000f49824a00230afb112a4392/opentelemetry_proto-1.40.0.tar.gz", hash = "sha256:03f639ca129ba513f5819810f5b1f42bcb371391405d99c168fe6937c62febcd", size = 45667, upload-time = "2026-03-04T14:17:31.194Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4b/7f/15f014fb195da6c2dbb6c71399b8e76824878718e94de6454038488eed28/opentelemetry_proto-1.45.1.tar.gz", hash = "sha256:79e0fb95e4616691a469439238aa9224d75779b3e108e895d1aa125ab29ca77c", size = 46488, upload-time = "2026-10-06T17:33:11.49Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b9/b2/189b2577dde745b15625b3214302605b1353436219d42b7912e77fa8dc24/opentelemetry_proto-1.40.0-py3-none-any.whl", hash = "sha256:266c4385d88923a23d63e353e9761af0f47a6ed0d486979777fe4de59dc9b25f", size = 72073, upload-time = "2026-03-04T14:17:16.673Z" }, + { url = "https://files.pythonhosted.org/packages/ab/9a/42ec8180a769516ae757e893b69736826efceac7332553915b4528a91c6d/opentelemetry_proto-1.45.1-py3-none-any.whl", hash = "sha256:f38e2a8413053c180cd3d2637fbb279673ec2f6a6e09c995aafa2f452c52b46e", size = 72488, upload-time = "2026-10-06T17:32:53.057Z" }, ] [[package]] name = "opentelemetry-sdk" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, { name = "opentelemetry-semantic-conventions" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/58/fd/3c3125b20ba18ce2155ba9ea74acb0ae5d25f8cd39cfd37455601b7955cc/opentelemetry_sdk-1.40.0.tar.gz", hash = "sha256:18e9f5ec20d859d268c7cb3c5198c8d105d073714db3de50b593b8c1345a48f2", size = 184252, upload-time = "2026-03-04T14:17:31.87Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/79/7392e21a1c8f0c61d90b223e31c7e48cb9d452e91a6b820ad24cca5f23c4/opentelemetry_sdk-1.45.1.tar.gz", hash = "sha256:63d24a6ca645019a631e6a51999c73e93adcac1196ca640b8ae78a7cc4762bf3", size = 218324, upload-time = "2026-10-06T17:33:13.26Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/c5/6a852903d8bfac758c6dc6e9a68b015d3c33f2f1be5e9591e0f4b69c7e0a/opentelemetry_sdk-1.40.0-py3-none-any.whl", hash = "sha256:787d2154a71f4b3d81f20524a8ce061b7db667d24e46753f32a7bc48f1c1f3f1", size = 141951, upload-time = "2026-03-04T14:17:17.961Z" }, + { url = "https://files.pythonhosted.org/packages/95/3c/87c42b4bd6dd297536f04cd9383d212ac557ecd49f2cbdcd46da1c9ef5c8/opentelemetry_sdk-1.45.1-py3-none-any.whl", hash = "sha256:c604c11dc429810812348989115fa44bd558772a3d7442afc43d024f2c250ca4", size = 140063, upload-time = "2026-10-06T17:32:55.04Z" }, ] [[package]] name = "opentelemetry-semantic-conventions" -version = "0.61b0" +version = "0.66b1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6d/c0/4ae7973f3c2cfd2b6e321f1675626f0dab0a97027cc7a297474c9c8f3d04/opentelemetry_semantic_conventions-0.61b0.tar.gz", hash = "sha256:072f65473c5d7c6dc0355b27d6c9d1a679d63b6d4b4b16a9773062cb7e31192a", size = 145755, upload-time = "2026-03-04T14:17:32.664Z" } +sdist = { url = "https://files.pythonhosted.org/packages/46/e4/dbbfb2a010c4db2224a5114638acede6fe563d33cc20fb1752cebcbe6298/opentelemetry_semantic_conventions-0.66b1.tar.gz", hash = "sha256:497ca63bf383723411e8eaf60c8779e9877633c936bb641080adab59d0eb6ec8", size = 150250, upload-time = "2026-10-06T17:33:14.073Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b2/37/cc6a55e448deaa9b27377d087da8615a3416d8ad523d5960b78dbeadd02a/opentelemetry_semantic_conventions-0.61b0-py3-none-any.whl", hash = "sha256:fa530a96be229795f8cef353739b618148b0fe2b4b3f005e60e262926c4d38e2", size = 231621, upload-time = "2026-03-04T14:17:19.33Z" }, + { url = "https://files.pythonhosted.org/packages/bc/14/67f8aa798857f8cf686f515bf93d9bb877ce952ddc8efae0fa25b45ce0d6/opentelemetry_semantic_conventions-0.66b1-py3-none-any.whl", hash = "sha256:d4cddeb4315490b35213f55e2bdc9ac54bb1e4d318927475bed62b35545e581b", size = 206279, upload-time = "2026-10-06T17:32:56.103Z" }, ] [[package]] From 31dab556b8bf18bdfa0576517fdda1aab5fa42f1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 23 Oct 2026 12:24:19 -0400 Subject: [PATCH 20/60] chore(deps): bump djangorestframework from 3.18.1 to 3.18.2 (#1118) Bumps [djangorestframework](https://github.com/encode/django-rest-framework) from 3.18.1 to 3.18.2. - [Release notes](https://github.com/encode/django-rest-framework/releases) - [Commits](https://github.com/encode/django-rest-framework/compare/3.18.1...3.18.2) --- updated-dependencies: - dependency-name: djangorestframework dependency-version: 3.18.2 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> (cherry picked from commit 125b26654ea317ce72077b7f3eb83d11461cf9b2) --- uv.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/uv.lock b/uv.lock index 7663437b..85c80de1 100644 --- a/uv.lock +++ b/uv.lock @@ -958,14 +958,14 @@ wheels = [ [[package]] name = "djangorestframework" -version = "3.18.1" +version = "3.18.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "django" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8a/2e/b3ce9d449b1ed9f9dd74fb7dfbc5f20860d5f40c2b4b10a2c3eabd8ff579/djangorestframework-3.18.1.tar.gz", hash = "sha256:605d79fa2ec2f02905492e5ea13d903c2d842d0b4c915a57f7bf02ab9f3c91dd", size = 915653, upload-time = "2026-09-07T18:04:08.288Z" } +sdist = { url = "https://files.pythonhosted.org/packages/3f/8b/2f048189610d58b29ae947f46d9c7d19c20ba427568eac906d6a2277d391/djangorestframework-3.18.2.tar.gz", hash = "sha256:8249bad7715eb37274ce5820fc5d9fd786b7579806589565f52a71c02b35f54b", size = 923551, upload-time = "2026-10-06T20:42:15.116Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e8/83/5ed615e47339d8e65f62eb1a8133f8f046492eb3a25ad40d90acb9a208b3/djangorestframework-3.18.1-py3-none-any.whl", hash = "sha256:f1409d698967aaf82d5d98d76b549c8fba8bd92ebd0d83d24454508f72168dc2", size = 901373, upload-time = "2026-09-07T18:04:06.237Z" }, + { url = "https://files.pythonhosted.org/packages/fa/9e/81e5bfbba54f7396233df979befde49e6e13c94c2df21b94555684dddd3e/djangorestframework-3.18.2-py3-none-any.whl", hash = "sha256:a92d6a4e128a2550bd2fe2e02008a9e47fe52c180e130bb438f5ba70ee933a3f", size = 903990, upload-time = "2026-10-06T20:42:13.411Z" }, ] [[package]] From fe16e1d698ac45f9065a884e85b4c8ac25cc2488 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 23 Oct 2026 12:24:30 -0400 Subject: [PATCH 21/60] chore(deps): bump the uv group across 31 directories with 1 update (#1119) Bumps the uv group with 1 update in the /examples/example-ai-anthropic directory: [opentelemetry-api](https://github.com/open-telemetry/opentelemetry-python). Bumps the uv group with 1 update in the /examples/example-ai-autogen directory: [opentelemetry-api](https://github.com/open-telemetry/opentelemetry-python). Bumps the uv group with 1 update in the /examples/example-ai-aws-bedrock directory: [opentelemetry-api](https://github.com/open-telemetry/opentelemetry-python). Bumps the uv group with 1 update in the /examples/example-ai-azure-openai directory: [opentelemetry-api](https://github.com/open-telemetry/opentelemetry-python). Bumps the uv group with 1 update in the /examples/example-ai-cerebras directory: [opentelemetry-api](https://github.com/open-telemetry/opentelemetry-python). Bumps the uv group with 1 update in the /examples/example-ai-cloudflare-ai-gateway directory: [opentelemetry-api](https://github.com/open-telemetry/opentelemetry-python). Bumps the uv group with 1 update in the /examples/example-ai-cohere directory: [opentelemetry-api](https://github.com/open-telemetry/opentelemetry-python). Bumps the uv group with 1 update in the /examples/example-ai-crewai directory: [opentelemetry-api](https://github.com/open-telemetry/opentelemetry-python). Bumps the uv group with 1 update in the /examples/example-ai-dedalus directory: [opentelemetry-api](https://github.com/open-telemetry/opentelemetry-python). Bumps the uv group with 1 update in the /examples/example-ai-deepseek directory: [opentelemetry-api](https://github.com/open-telemetry/opentelemetry-python). Bumps the uv group with 1 update in the /examples/example-ai-fireworks-ai directory: [opentelemetry-api](https://github.com/open-telemetry/opentelemetry-python). Bumps the uv group with 1 update in the /examples/example-ai-gemini directory: [opentelemetry-api](https://github.com/open-telemetry/opentelemetry-python). Bumps the uv group with 1 update in the /examples/example-ai-groq directory: [opentelemetry-api](https://github.com/open-telemetry/opentelemetry-python). Bumps the uv group with 1 update in the /examples/example-ai-helicone directory: [opentelemetry-api](https://github.com/open-telemetry/opentelemetry-python). Bumps the uv group with 1 update in the /examples/example-ai-hugging-face directory: [opentelemetry-api](https://github.com/open-telemetry/opentelemetry-python). Bumps the uv group with 1 update in the /examples/example-ai-instructor directory: [opentelemetry-api](https://github.com/open-telemetry/opentelemetry-python). Bumps the uv group with 1 update in the /examples/example-ai-langchain directory: [opentelemetry-api](https://github.com/open-telemetry/opentelemetry-python). Bumps the uv group with 1 update in the /examples/example-ai-langgraph directory: [opentelemetry-api](https://github.com/open-telemetry/opentelemetry-python). Bumps the uv group with 1 update in the /examples/example-ai-llamaindex directory: [opentelemetry-api](https://github.com/open-telemetry/opentelemetry-python). Bumps the uv group with 1 update in the /examples/example-ai-mirascope directory: [opentelemetry-api](https://github.com/open-telemetry/opentelemetry-python). Bumps the uv group with 1 update in the /examples/example-ai-mistral directory: [opentelemetry-api](https://github.com/open-telemetry/opentelemetry-python). Bumps the uv group with 1 update in the /examples/example-ai-ollama directory: [opentelemetry-api](https://github.com/open-telemetry/opentelemetry-python). Bumps the uv group with 1 update in the /examples/example-ai-openai directory: [opentelemetry-api](https://github.com/open-telemetry/opentelemetry-python). Bumps the uv group with 1 update in the /examples/example-ai-openrouter directory: [opentelemetry-api](https://github.com/open-telemetry/opentelemetry-python). Bumps the uv group with 1 update in the /examples/example-ai-perplexity directory: [opentelemetry-api](https://github.com/open-telemetry/opentelemetry-python). Bumps the uv group with 1 update in the /examples/example-ai-portkey directory: [opentelemetry-api](https://github.com/open-telemetry/opentelemetry-python). Bumps the uv group with 1 update in the /examples/example-ai-semantic-kernel directory: [opentelemetry-api](https://github.com/open-telemetry/opentelemetry-python). Bumps the uv group with 1 update in the /examples/example-ai-smolagents directory: [opentelemetry-api](https://github.com/open-telemetry/opentelemetry-python). Bumps the uv group with 1 update in the /examples/example-ai-together-ai directory: [opentelemetry-api](https://github.com/open-telemetry/opentelemetry-python). Bumps the uv group with 1 update in the /examples/example-ai-vercel-ai-gateway directory: [opentelemetry-api](https://github.com/open-telemetry/opentelemetry-python). Bumps the uv group with 1 update in the /examples/example-ai-xai directory: [opentelemetry-api](https://github.com/open-telemetry/opentelemetry-python). Updates `opentelemetry-api` from 1.40.0 to 1.45.1 - [Release notes](https://github.com/open-telemetry/opentelemetry-python/releases) - [Changelog](https://github.com/open-telemetry/opentelemetry-python/blob/main/CHANGELOG.md) - [Commits](https://github.com/open-telemetry/opentelemetry-python/compare/v1.40.0...v1.45.1) Updates `opentelemetry-api` from 1.40.0 to 1.45.1 - [Release notes](https://github.com/open-telemetry/opentelemetry-python/releases) - [Changelog](https://github.com/open-telemetry/opentelemetry-python/blob/main/CHANGELOG.md) - [Commits](https://github.com/open-telemetry/opentelemetry-python/compare/v1.40.0...v1.45.1) Updates `opentelemetry-api` from 1.40.0 to 1.45.1 - [Release notes](https://github.com/open-telemetry/opentelemetry-python/releases) - [Changelog](https://github.com/open-telemetry/opentelemetry-python/blob/main/CHANGELOG.md) - [Commits](https://github.com/open-telemetry/opentelemetry-python/compare/v1.40.0...v1.45.1) Updates `opentelemetry-api` from 1.40.0 to 1.45.1 - [Release notes](https://github.com/open-telemetry/opentelemetry-python/releases) - [Changelog](https://github.com/open-telemetry/opentelemetry-python/blob/main/CHANGELOG.md) - [Commits](https://github.com/open-telemetry/opentelemetry-python/compare/v1.40.0...v1.45.1) Updates `opentelemetry-api` from 1.40.0 to 1.45.1 - [Release notes](https://github.com/open-telemetry/opentelemetry-python/releases) - [Changelog](https://github.com/open-telemetry/opentelemetry-python/blob/main/CHANGELOG.md) - [Commits](https://github.com/open-telemetry/opentelemetry-python/compare/v1.40.0...v1.45.1) Updates `opentelemetry-api` from 1.40.0 to 1.45.1 - [Release notes](https://github.com/open-telemetry/opentelemetry-python/releases) - [Changelog](https://github.com/open-telemetry/opentelemetry-python/blob/main/CHANGELOG.md) - [Commits](https://github.com/open-telemetry/opentelemetry-python/compare/v1.40.0...v1.45.1) Updates `opentelemetry-api` from 1.40.0 to 1.45.1 - [Release notes](https://github.com/open-telemetry/opentelemetry-python/releases) - [Changelog](https://github.com/open-telemetry/opentelemetry-python/blob/main/CHANGELOG.md) - [Commits](https://github.com/open-telemetry/opentelemetry-python/compare/v1.40.0...v1.45.1) Updates `opentelemetry-api` from 1.34.1 to 1.45.1 - [Release notes](https://github.com/open-telemetry/opentelemetry-python/releases) - [Changelog](https://github.com/open-telemetry/opentelemetry-python/blob/main/CHANGELOG.md) - [Commits](https://github.com/open-telemetry/opentelemetry-python/compare/v1.40.0...v1.45.1) Updates `opentelemetry-api` from 1.40.0 to 1.45.1 - [Release notes](https://github.com/open-telemetry/opentelemetry-python/releases) - [Changelog](https://github.com/open-telemetry/opentelemetry-python/blob/main/CHANGELOG.md) - [Commits](https://github.com/open-telemetry/opentelemetry-python/compare/v1.40.0...v1.45.1) Updates `opentelemetry-api` from 1.40.0 to 1.45.1 - [Release notes](https://github.com/open-telemetry/opentelemetry-python/releases) - [Changelog](https://github.com/open-telemetry/opentelemetry-python/blob/main/CHANGELOG.md) - [Commits](https://github.com/open-telemetry/opentelemetry-python/compare/v1.40.0...v1.45.1) Updates `opentelemetry-api` from 1.40.0 to 1.45.1 - [Release notes](https://github.com/open-telemetry/opentelemetry-python/releases) - [Changelog](https://github.com/open-telemetry/opentelemetry-python/blob/main/CHANGELOG.md) - [Commits](https://github.com/open-telemetry/opentelemetry-python/compare/v1.40.0...v1.45.1) Updates `opentelemetry-api` from 1.40.0 to 1.45.1 - [Release notes](https://github.com/open-telemetry/opentelemetry-python/releases) - [Changelog](https://github.com/open-telemetry/opentelemetry-python/blob/main/CHANGELOG.md) - [Commits](https://github.com/open-telemetry/opentelemetry-python/compare/v1.40.0...v1.45.1) Updates `opentelemetry-api` from 1.40.0 to 1.45.1 - [Release notes](https://github.com/open-telemetry/opentelemetry-python/releases) - [Changelog](https://github.com/open-telemetry/opentelemetry-python/blob/main/CHANGELOG.md) - [Commits](https://github.com/open-telemetry/opentelemetry-python/compare/v1.40.0...v1.45.1) Updates `opentelemetry-api` from 1.40.0 to 1.45.1 - [Release notes](https://github.com/open-telemetry/opentelemetry-python/releases) - [Changelog](https://github.com/open-telemetry/opentelemetry-python/blob/main/CHANGELOG.md) - [Commits](https://github.com/open-telemetry/opentelemetry-python/compare/v1.40.0...v1.45.1) Updates `opentelemetry-api` from 1.40.0 to 1.45.1 - [Release notes](https://github.com/open-telemetry/opentelemetry-python/releases) - [Changelog](https://github.com/open-telemetry/opentelemetry-python/blob/main/CHANGELOG.md) - [Commits](https://github.com/open-telemetry/opentelemetry-python/compare/v1.40.0...v1.45.1) Updates `opentelemetry-api` from 1.40.0 to 1.45.1 - [Release notes](https://github.com/open-telemetry/opentelemetry-python/releases) - [Changelog](https://github.com/open-telemetry/opentelemetry-python/blob/main/CHANGELOG.md) - [Commits](https://github.com/open-telemetry/opentelemetry-python/compare/v1.40.0...v1.45.1) Updates `opentelemetry-api` from 1.40.0 to 1.45.1 - [Release notes](https://github.com/open-telemetry/opentelemetry-python/releases) - [Changelog](https://github.com/open-telemetry/opentelemetry-python/blob/main/CHANGELOG.md) - [Commits](https://github.com/open-telemetry/opentelemetry-python/compare/v1.40.0...v1.45.1) Updates `opentelemetry-api` from 1.40.0 to 1.45.1 - [Release notes](https://github.com/open-telemetry/opentelemetry-python/releases) - [Changelog](https://github.com/open-telemetry/opentelemetry-python/blob/main/CHANGELOG.md) - [Commits](https://github.com/open-telemetry/opentelemetry-python/compare/v1.40.0...v1.45.1) Updates `opentelemetry-api` from 1.40.0 to 1.45.1 - [Release notes](https://github.com/open-telemetry/opentelemetry-python/releases) - [Changelog](https://github.com/open-telemetry/opentelemetry-python/blob/main/CHANGELOG.md) - [Commits](https://github.com/open-telemetry/opentelemetry-python/compare/v1.40.0...v1.45.1) Updates `opentelemetry-api` from 1.40.0 to 1.45.1 - [Release notes](https://github.com/open-telemetry/opentelemetry-python/releases) - [Changelog](https://github.com/open-telemetry/opentelemetry-python/blob/main/CHANGELOG.md) - [Commits](https://github.com/open-telemetry/opentelemetry-python/compare/v1.40.0...v1.45.1) Updates `opentelemetry-api` from 1.40.0 to 1.45.1 - [Release notes](https://github.com/open-telemetry/opentelemetry-python/releases) - [Changelog](https://github.com/open-telemetry/opentelemetry-python/blob/main/CHANGELOG.md) - [Commits](https://github.com/open-telemetry/opentelemetry-python/compare/v1.40.0...v1.45.1) Updates `opentelemetry-api` from 1.40.0 to 1.45.1 - [Release notes](https://github.com/open-telemetry/opentelemetry-python/releases) - [Changelog](https://github.com/open-telemetry/opentelemetry-python/blob/main/CHANGELOG.md) - [Commits](https://github.com/open-telemetry/opentelemetry-python/compare/v1.40.0...v1.45.1) Updates `opentelemetry-api` from 1.40.0 to 1.45.1 - [Release notes](https://github.com/open-telemetry/opentelemetry-python/releases) - [Changelog](https://github.com/open-telemetry/opentelemetry-python/blob/main/CHANGELOG.md) - [Commits](https://github.com/open-telemetry/opentelemetry-python/compare/v1.40.0...v1.45.1) Updates `opentelemetry-api` from 1.40.0 to 1.45.1 - [Release notes](https://github.com/open-telemetry/opentelemetry-python/releases) - [Changelog](https://github.com/open-telemetry/opentelemetry-python/blob/main/CHANGELOG.md) - [Commits](https://github.com/open-telemetry/opentelemetry-python/compare/v1.40.0...v1.45.1) Updates `opentelemetry-api` from 1.40.0 to 1.45.1 - [Release notes](https://github.com/open-telemetry/opentelemetry-python/releases) - [Changelog](https://github.com/open-telemetry/opentelemetry-python/blob/main/CHANGELOG.md) - [Commits](https://github.com/open-telemetry/opentelemetry-python/compare/v1.40.0...v1.45.1) Updates `opentelemetry-api` from 1.40.0 to 1.45.1 - [Release notes](https://github.com/open-telemetry/opentelemetry-python/releases) - [Changelog](https://github.com/open-telemetry/opentelemetry-python/blob/main/CHANGELOG.md) - [Commits](https://github.com/open-telemetry/opentelemetry-python/compare/v1.40.0...v1.45.1) Updates `opentelemetry-api` from 1.40.0 to 1.45.1 - [Release notes](https://github.com/open-telemetry/opentelemetry-python/releases) - [Changelog](https://github.com/open-telemetry/opentelemetry-python/blob/main/CHANGELOG.md) - [Commits](https://github.com/open-telemetry/opentelemetry-python/compare/v1.40.0...v1.45.1) Updates `opentelemetry-api` from 1.40.0 to 1.45.1 - [Release notes](https://github.com/open-telemetry/opentelemetry-python/releases) - [Changelog](https://github.com/open-telemetry/opentelemetry-python/blob/main/CHANGELOG.md) - [Commits](https://github.com/open-telemetry/opentelemetry-python/compare/v1.40.0...v1.45.1) Updates `opentelemetry-api` from 1.40.0 to 1.45.1 - [Release notes](https://github.com/open-telemetry/opentelemetry-python/releases) - [Changelog](https://github.com/open-telemetry/opentelemetry-python/blob/main/CHANGELOG.md) - [Commits](https://github.com/open-telemetry/opentelemetry-python/compare/v1.40.0...v1.45.1) Updates `opentelemetry-api` from 1.40.0 to 1.45.1 - [Release notes](https://github.com/open-telemetry/opentelemetry-python/releases) - [Changelog](https://github.com/open-telemetry/opentelemetry-python/blob/main/CHANGELOG.md) - [Commits](https://github.com/open-telemetry/opentelemetry-python/compare/v1.40.0...v1.45.1) Updates `opentelemetry-api` from 1.40.0 to 1.45.1 - [Release notes](https://github.com/open-telemetry/opentelemetry-python/releases) - [Changelog](https://github.com/open-telemetry/opentelemetry-python/blob/main/CHANGELOG.md) - [Commits](https://github.com/open-telemetry/opentelemetry-python/compare/v1.40.0...v1.45.1) --- updated-dependencies: - dependency-name: opentelemetry-api dependency-version: 1.45.1 dependency-type: indirect dependency-group: uv - dependency-name: opentelemetry-api dependency-version: 1.45.1 dependency-type: indirect dependency-group: uv - dependency-name: opentelemetry-api dependency-version: 1.45.1 dependency-type: indirect dependency-group: uv - dependency-name: opentelemetry-api dependency-version: 1.45.1 dependency-type: indirect dependency-group: uv - dependency-name: opentelemetry-api dependency-version: 1.45.1 dependency-type: indirect dependency-group: uv - dependency-name: opentelemetry-api dependency-version: 1.45.1 dependency-type: indirect dependency-group: uv - dependency-name: opentelemetry-api dependency-version: 1.45.1 dependency-type: indirect dependency-group: uv - dependency-name: opentelemetry-api dependency-version: 1.45.1 dependency-type: indirect dependency-group: uv - dependency-name: opentelemetry-api dependency-version: 1.45.1 dependency-type: indirect dependency-group: uv - dependency-name: opentelemetry-api dependency-version: 1.45.1 dependency-type: indirect dependency-group: uv - dependency-name: opentelemetry-api dependency-version: 1.45.1 dependency-type: indirect dependency-group: uv - dependency-name: opentelemetry-api dependency-version: 1.45.1 dependency-type: indirect dependency-group: uv - dependency-name: opentelemetry-api dependency-version: 1.45.1 dependency-type: indirect dependency-group: uv - dependency-name: opentelemetry-api dependency-version: 1.45.1 dependency-type: indirect dependency-group: uv - dependency-name: opentelemetry-api dependency-version: 1.45.1 dependency-type: indirect dependency-group: uv - dependency-name: opentelemetry-api dependency-version: 1.45.1 dependency-type: indirect dependency-group: uv - dependency-name: opentelemetry-api dependency-version: 1.45.1 dependency-type: indirect dependency-group: uv - dependency-name: opentelemetry-api dependency-version: 1.45.1 dependency-type: indirect dependency-group: uv - dependency-name: opentelemetry-api dependency-version: 1.45.1 dependency-type: indirect dependency-group: uv - dependency-name: opentelemetry-api dependency-version: 1.45.1 dependency-type: indirect dependency-group: uv - dependency-name: opentelemetry-api dependency-version: 1.45.1 dependency-type: indirect dependency-group: uv - dependency-name: opentelemetry-api dependency-version: 1.45.1 dependency-type: indirect dependency-group: uv - dependency-name: opentelemetry-api dependency-version: 1.45.1 dependency-type: indirect dependency-group: uv - dependency-name: opentelemetry-api dependency-version: 1.45.1 dependency-type: indirect dependency-group: uv - dependency-name: opentelemetry-api dependency-version: 1.45.1 dependency-type: indirect dependency-group: uv - dependency-name: opentelemetry-api dependency-version: 1.45.1 dependency-type: indirect dependency-group: uv - dependency-name: opentelemetry-api dependency-version: 1.45.1 dependency-type: indirect dependency-group: uv - dependency-name: opentelemetry-api dependency-version: 1.45.1 dependency-type: indirect dependency-group: uv - dependency-name: opentelemetry-api dependency-version: 1.45.1 dependency-type: indirect dependency-group: uv - dependency-name: opentelemetry-api dependency-version: 1.45.1 dependency-type: indirect dependency-group: uv - dependency-name: opentelemetry-api dependency-version: 1.45.1 dependency-type: indirect dependency-group: uv ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> (cherry picked from commit 3f6c80eb4df1ddffd9e5b9d1d8aae8facf9f83b2) --- examples/example-ai-anthropic/uv.lock | 97 +++++++++-------- examples/example-ai-autogen/uv.lock | 97 +++++++++-------- examples/example-ai-aws-bedrock/uv.lock | 102 ++++++++++-------- examples/example-ai-azure-openai/uv.lock | 97 +++++++++-------- examples/example-ai-cerebras/uv.lock | 97 +++++++++-------- .../example-ai-cloudflare-ai-gateway/uv.lock | 97 +++++++++-------- examples/example-ai-cohere/uv.lock | 97 +++++++++-------- examples/example-ai-crewai/uv.lock | 75 +++++++++---- examples/example-ai-dedalus/uv.lock | 97 +++++++++-------- examples/example-ai-deepseek/uv.lock | 97 +++++++++-------- examples/example-ai-fireworks-ai/uv.lock | 97 +++++++++-------- examples/example-ai-gemini/uv.lock | 87 ++++++++------- examples/example-ai-groq/uv.lock | 97 +++++++++-------- examples/example-ai-helicone/uv.lock | 97 +++++++++-------- examples/example-ai-hugging-face/uv.lock | 97 +++++++++-------- examples/example-ai-instructor/uv.lock | 97 +++++++++-------- examples/example-ai-langchain/uv.lock | 97 +++++++++-------- examples/example-ai-langgraph/uv.lock | 97 +++++++++-------- examples/example-ai-llamaindex/uv.lock | 97 +++++++++-------- examples/example-ai-mirascope/uv.lock | 97 +++++++++-------- examples/example-ai-mistral/uv.lock | 97 +++++++++-------- examples/example-ai-ollama/uv.lock | 97 +++++++++-------- examples/example-ai-openai/uv.lock | 97 +++++++++-------- examples/example-ai-openrouter/uv.lock | 97 +++++++++-------- examples/example-ai-perplexity/uv.lock | 97 +++++++++-------- examples/example-ai-portkey/uv.lock | 97 +++++++++-------- examples/example-ai-semantic-kernel/uv.lock | 97 +++++++++-------- examples/example-ai-smolagents/uv.lock | 97 +++++++++-------- examples/example-ai-together-ai/uv.lock | 97 +++++++++-------- examples/example-ai-vercel-ai-gateway/uv.lock | 97 +++++++++-------- examples/example-ai-xai/uv.lock | 97 +++++++++-------- 31 files changed, 1641 insertions(+), 1339 deletions(-) diff --git a/examples/example-ai-anthropic/uv.lock b/examples/example-ai-anthropic/uv.lock index 2162e289..7c08e365 100644 --- a/examples/example-ai-anthropic/uv.lock +++ b/examples/example-ai-anthropic/uv.lock @@ -281,18 +281,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/58/a2/bb081bab032533a855d44de1d56f8e8426114ff1ba5d1f07a438a0a654f8/idna-3.20-py3-none-any.whl", hash = "sha256:ab7ae7122974553370f0bdb919e1a960b2cd1bc1ef0276416d896db81c14582c", size = 69583, upload-time = "2026-09-17T14:11:03.168Z" }, ] -[[package]] -name = "importlib-metadata" -version = "8.7.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "zipp" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/f3/49/3b30cad09e7771a4982d9975a8cbf64f00d4a1ececb53297f1d9a7be1b10/importlib_metadata-8.7.1.tar.gz", hash = "sha256:49fef1ae6440c182052f407c8d34a68f72efc36db9ca90dc0113398f2fdde8bb", size = 57107, upload-time = "2025-12-21T10:00:19.278Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fa/5e/f8e9a1d23b9c20a551a8a02ea3637b4642e22c2626e3a13a9a29cdea99eb/importlib_metadata-8.7.1-py3-none-any.whl", hash = "sha256:5a1f80bf1daa489495071efbb095d75a634cf28a8bc299581244063b53176151", size = 27865, upload-time = "2025-12-21T10:00:18.329Z" }, -] - [[package]] name = "jiter" version = "0.13.0" @@ -392,50 +380,80 @@ wheels = [ [[package]] name = "opentelemetry-api" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "importlib-metadata" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2c/1d/4049a9e8698361cc1a1aa03a6c59e4fa4c71e0c0f94a30f988a6876a2ae6/opentelemetry_api-1.40.0.tar.gz", hash = "sha256:159be641c0b04d11e9ecd576906462773eb97ae1b657730f0ecf64d32071569f", size = 70851, upload-time = "2026-03-04T14:17:21.555Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2e/02/6e0ae9cc61bd3169d401077b507b3ebc344745171e1051ab430be012dcd9/opentelemetry_api-1.45.1.tar.gz", hash = "sha256:aa38ed19bcc084ba42782a73255b3582283eced7ad6dddbd6695189e69adfb75", size = 72804, upload-time = "2026-10-06T17:32:58.133Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/41/f7dcf80b81ee8e71c1a2b59f14208bc723edbd89ed027a73b175abf6348e/opentelemetry_api-1.45.1-py3-none-any.whl", hash = "sha256:b31553efa588ae44bc306f863c785c5333a9ecc091248c6ee68b4b6c87fdedfb", size = 60256, upload-time = "2026-10-06T17:32:33.506Z" }, +] + +[[package]] +name = "opentelemetry-exporter-http-transport" +version = "0.66b1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/62/0c/e3ebdb4b507f66afcc905e6885a4946969bd75b45988492643356fbbdc63/opentelemetry_exporter_http_transport-0.66b1.tar.gz", hash = "sha256:443080203bf52586ce0b2ad901e8951c61833eab1aa539ae6f1f16fe9e8e7952", size = 11693, upload-time = "2026-10-06T17:32:59.65Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/69/6af86ff66492b481c6a4c05dcfd68beb47ed8ba046440a26a2aac76b95c7/opentelemetry_exporter_http_transport-0.66b1-py3-none-any.whl", hash = "sha256:2f95404bdee7f9d2d529c7de56c7bd86d014d774d8fbf137810e0167f8a492bf", size = 12155, upload-time = "2026-10-06T17:32:35.454Z" }, +] + +[package.optional-dependencies] +requests = [ + { name = "requests" }, +] + +[[package]] +name = "opentelemetry-exporter-otlp-common" +version = "0.66b1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-sdk" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cb/19/41de712173f43057e4532d42ece7d0c6d4210d353e5752433cb14987643f/opentelemetry_exporter_otlp_common-0.66b1.tar.gz", hash = "sha256:6b1403487a2185ac1feb45fd5546fdf8630ce71c36bcefaadf51e2130e9e23f9", size = 14325, upload-time = "2026-10-06T17:33:01.725Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5f/bf/93795954016c522008da367da292adceed71cca6ee1717e1d64c83089099/opentelemetry_api-1.40.0-py3-none-any.whl", hash = "sha256:82dd69331ae74b06f6a874704be0cfaa49a1650e1537d4a813b86ecef7d0ecf9", size = 68676, upload-time = "2026-03-04T14:17:01.24Z" }, + { url = "https://files.pythonhosted.org/packages/fc/39/8c23d67665c762aa51840fa06f86e902e8f6f1693bc8d7e3d98cd6e2f753/opentelemetry_exporter_otlp_common-0.66b1-py3-none-any.whl", hash = "sha256:00ff8592c3a7cb729ff3fdc7ffa12372c243bdf2163e80c180994d0c7bd83ee9", size = 12385, upload-time = "2026-10-06T17:32:38.177Z" }, ] [[package]] name = "opentelemetry-exporter-otlp-proto-common" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-proto" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/51/bc/1559d46557fe6eca0b46c88d4c2676285f1f3be2e8d06bb5d15fbffc814a/opentelemetry_exporter_otlp_proto_common-1.40.0.tar.gz", hash = "sha256:1cbee86a4064790b362a86601ee7934f368b81cd4cc2f2e163902a6e7818a0fa", size = 20416, upload-time = "2026-03-04T14:17:23.801Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c1/8e/65e85e5137991a3c493b11682151d198638a5bc1dd4b4c5f67e013c57d7c/opentelemetry_exporter_otlp_proto_common-1.45.1.tar.gz", hash = "sha256:2e4adcc3a67bcf57804fc49514f0ef64974ca7590aa3491da389852b4a0628f6", size = 18873, upload-time = "2026-10-06T17:33:04.471Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8b/ca/8f122055c97a932311a3f640273f084e738008933503d0c2563cd5d591fc/opentelemetry_exporter_otlp_proto_common-1.40.0-py3-none-any.whl", hash = "sha256:7081ff453835a82417bf38dccf122c827c3cbc94f2079b03bba02a3165f25149", size = 18369, upload-time = "2026-03-04T14:17:04.796Z" }, + { url = "https://files.pythonhosted.org/packages/84/aa/92f225d353904e7f70b8b3e3c1b02db0cf56f744c2e83c581dc372e78873/opentelemetry_exporter_otlp_proto_common-1.45.1-py3-none-any.whl", hash = "sha256:2f446183ae7047b036226f1d846c41a834b0e8755ad13b51a51dd38952eb466c", size = 15393, upload-time = "2026-10-06T17:32:41.911Z" }, ] [[package]] name = "opentelemetry-exporter-otlp-proto-http" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "googleapis-common-protos" }, { name = "opentelemetry-api" }, + { name = "opentelemetry-exporter-http-transport", extra = ["requests"] }, + { name = "opentelemetry-exporter-otlp-common" }, { name = "opentelemetry-exporter-otlp-proto-common" }, { name = "opentelemetry-proto" }, { name = "opentelemetry-sdk" }, { name = "requests" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2e/fa/73d50e2c15c56be4d000c98e24221d494674b0cc95524e2a8cb3856d95a4/opentelemetry_exporter_otlp_proto_http-1.40.0.tar.gz", hash = "sha256:db48f5e0f33217588bbc00274a31517ba830da576e59503507c839b38fa0869c", size = 17772, upload-time = "2026-03-04T14:17:25.324Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/17/26487707ea4caa97b17e6e4b5fa72133a53512ffa2f5cf7a49ef284b29cb/opentelemetry_exporter_otlp_proto_http-1.45.1.tar.gz", hash = "sha256:45c218405ce3fd879596924b1874bf9a8f6880206d61065c5a912c8e5c297fb7", size = 28839, upload-time = "2026-10-06T17:33:05.713Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/3a/8865d6754e61c9fb170cdd530a124a53769ee5f740236064816eb0ca7301/opentelemetry_exporter_otlp_proto_http-1.40.0-py3-none-any.whl", hash = "sha256:a8d1dab28f504c5d96577d6509f80a8150e44e8f45f82cdbe0e34c99ab040069", size = 19960, upload-time = "2026-03-04T14:17:07.153Z" }, + { url = "https://files.pythonhosted.org/packages/aa/1f/517eaa0187ba106a9da97160ce2add3a371812681dc440930b267f714e42/opentelemetry_exporter_otlp_proto_http-1.45.1-py3-none-any.whl", hash = "sha256:24a97cf3753c7fb52fad44a696e452ff371686339e2acf3309e2eda3d0230700", size = 22180, upload-time = "2026-10-06T17:32:43.946Z" }, ] [[package]] name = "opentelemetry-instrumentation" -version = "0.61b0" +version = "0.66b1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, @@ -443,9 +461,9 @@ dependencies = [ { name = "packaging" }, { name = "wrapt" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/da/37/6bf8e66bfcee5d3c6515b79cb2ee9ad05fe573c20f7ceb288d0e7eeec28c/opentelemetry_instrumentation-0.61b0.tar.gz", hash = "sha256:cb21b48db738c9de196eba6b805b4ff9de3b7f187e4bbf9a466fa170514f1fc7", size = 32606, upload-time = "2026-03-04T14:20:16.825Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a5/03/89e47ff8d52a4f83b343e6eb9ef1698ff45357216e5b6b2b21e0da5c5c7d/opentelemetry_instrumentation-0.66b1.tar.gz", hash = "sha256:e79a510f7d87c72d95e964ddb42193a0d9a75668c027d980eab032ea1322a5ce", size = 43308, upload-time = "2026-10-06T17:36:10.703Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d8/3e/f6f10f178b6316de67f0dfdbbb699a24fbe8917cf1743c1595fb9dcdd461/opentelemetry_instrumentation-0.61b0-py3-none-any.whl", hash = "sha256:92a93a280e69788e8f88391247cc530fd81f16f2b011979d4d6398f805cfbc63", size = 33448, upload-time = "2026-03-04T14:19:02.447Z" }, + { url = "https://files.pythonhosted.org/packages/da/b2/d1413681ff43e13ac9860df27e1226d3199ab0b97b352ceea41abcc660a5/opentelemetry_instrumentation-0.66b1-py3-none-any.whl", hash = "sha256:4c4aa14dc9a24a02325a9d4c42c4d0208dbb1374c2b1b8fe6c9392d59f3e1008", size = 36904, upload-time = "2026-10-06T17:35:11.663Z" }, ] [[package]] @@ -465,41 +483,41 @@ wheels = [ [[package]] name = "opentelemetry-proto" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "protobuf" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4c/77/dd38991db037fdfce45849491cb61de5ab000f49824a00230afb112a4392/opentelemetry_proto-1.40.0.tar.gz", hash = "sha256:03f639ca129ba513f5819810f5b1f42bcb371391405d99c168fe6937c62febcd", size = 45667, upload-time = "2026-03-04T14:17:31.194Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4b/7f/15f014fb195da6c2dbb6c71399b8e76824878718e94de6454038488eed28/opentelemetry_proto-1.45.1.tar.gz", hash = "sha256:79e0fb95e4616691a469439238aa9224d75779b3e108e895d1aa125ab29ca77c", size = 46488, upload-time = "2026-10-06T17:33:11.49Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b9/b2/189b2577dde745b15625b3214302605b1353436219d42b7912e77fa8dc24/opentelemetry_proto-1.40.0-py3-none-any.whl", hash = "sha256:266c4385d88923a23d63e353e9761af0f47a6ed0d486979777fe4de59dc9b25f", size = 72073, upload-time = "2026-03-04T14:17:16.673Z" }, + { url = "https://files.pythonhosted.org/packages/ab/9a/42ec8180a769516ae757e893b69736826efceac7332553915b4528a91c6d/opentelemetry_proto-1.45.1-py3-none-any.whl", hash = "sha256:f38e2a8413053c180cd3d2637fbb279673ec2f6a6e09c995aafa2f452c52b46e", size = 72488, upload-time = "2026-10-06T17:32:53.057Z" }, ] [[package]] name = "opentelemetry-sdk" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, { name = "opentelemetry-semantic-conventions" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/58/fd/3c3125b20ba18ce2155ba9ea74acb0ae5d25f8cd39cfd37455601b7955cc/opentelemetry_sdk-1.40.0.tar.gz", hash = "sha256:18e9f5ec20d859d268c7cb3c5198c8d105d073714db3de50b593b8c1345a48f2", size = 184252, upload-time = "2026-03-04T14:17:31.87Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/79/7392e21a1c8f0c61d90b223e31c7e48cb9d452e91a6b820ad24cca5f23c4/opentelemetry_sdk-1.45.1.tar.gz", hash = "sha256:63d24a6ca645019a631e6a51999c73e93adcac1196ca640b8ae78a7cc4762bf3", size = 218324, upload-time = "2026-10-06T17:33:13.26Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/c5/6a852903d8bfac758c6dc6e9a68b015d3c33f2f1be5e9591e0f4b69c7e0a/opentelemetry_sdk-1.40.0-py3-none-any.whl", hash = "sha256:787d2154a71f4b3d81f20524a8ce061b7db667d24e46753f32a7bc48f1c1f3f1", size = 141951, upload-time = "2026-03-04T14:17:17.961Z" }, + { url = "https://files.pythonhosted.org/packages/95/3c/87c42b4bd6dd297536f04cd9383d212ac557ecd49f2cbdcd46da1c9ef5c8/opentelemetry_sdk-1.45.1-py3-none-any.whl", hash = "sha256:c604c11dc429810812348989115fa44bd558772a3d7442afc43d024f2c250ca4", size = 140063, upload-time = "2026-10-06T17:32:55.04Z" }, ] [[package]] name = "opentelemetry-semantic-conventions" -version = "0.61b0" +version = "0.66b1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6d/c0/4ae7973f3c2cfd2b6e321f1675626f0dab0a97027cc7a297474c9c8f3d04/opentelemetry_semantic_conventions-0.61b0.tar.gz", hash = "sha256:072f65473c5d7c6dc0355b27d6c9d1a679d63b6d4b4b16a9773062cb7e31192a", size = 145755, upload-time = "2026-03-04T14:17:32.664Z" } +sdist = { url = "https://files.pythonhosted.org/packages/46/e4/dbbfb2a010c4db2224a5114638acede6fe563d33cc20fb1752cebcbe6298/opentelemetry_semantic_conventions-0.66b1.tar.gz", hash = "sha256:497ca63bf383723411e8eaf60c8779e9877633c936bb641080adab59d0eb6ec8", size = 150250, upload-time = "2026-10-06T17:33:14.073Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b2/37/cc6a55e448deaa9b27377d087da8615a3416d8ad523d5960b78dbeadd02a/opentelemetry_semantic_conventions-0.61b0-py3-none-any.whl", hash = "sha256:fa530a96be229795f8cef353739b618148b0fe2b4b3f005e60e262926c4d38e2", size = 231621, upload-time = "2026-03-04T14:17:19.33Z" }, + { url = "https://files.pythonhosted.org/packages/bc/14/67f8aa798857f8cf686f515bf93d9bb877ce952ddc8efae0fa25b45ce0d6/opentelemetry_semantic_conventions-0.66b1-py3-none-any.whl", hash = "sha256:d4cddeb4315490b35213f55e2bdc9ac54bb1e4d318927475bed62b35545e581b", size = 206279, upload-time = "2026-10-06T17:32:56.103Z" }, ] [[package]] @@ -526,7 +544,7 @@ wheels = [ [[package]] name = "posthog" -version = "7.75.4" +version = "7.75.11" source = { directory = "../../" } dependencies = [ { name = "backoff" }, @@ -879,12 +897,3 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/46/78/10ad9781128ed2f99dbc474f43283b13fea8ba58723e98844367531c18e9/wrapt-1.17.3-cp314-cp314t-win_arm64.whl", hash = "sha256:f38e60678850c42461d4202739f9bf1e3a737c7ad283638251e79cc49effb6b6", size = 38471, upload-time = "2025-08-12T05:52:57.784Z" }, { url = "https://files.pythonhosted.org/packages/1f/f6/a933bd70f98e9cf3e08167fc5cd7aaaca49147e48411c0bd5ae701bb2194/wrapt-1.17.3-py3-none-any.whl", hash = "sha256:7171ae35d2c33d326ac19dd8facb1e82e5fd04ef8c6c0e394d7af55a55051c22", size = 23591, upload-time = "2025-08-12T05:53:20.674Z" }, ] - -[[package]] -name = "zipp" -version = "3.23.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166", size = 25547, upload-time = "2025-06-08T17:06:39.4Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276, upload-time = "2025-06-08T17:06:38.034Z" }, -] diff --git a/examples/example-ai-autogen/uv.lock b/examples/example-ai-autogen/uv.lock index 24352e92..b1cbe8a8 100644 --- a/examples/example-ai-autogen/uv.lock +++ b/examples/example-ai-autogen/uv.lock @@ -323,18 +323,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/57/b0/0e52c878c53f245edd3a11020f20979b3f490f245af532c7cae3027754b5/idna-3.19-py3-none-any.whl", hash = "sha256:815e7be7a7806d54abb586dc943addc79e8b2ee16915059658cbeff4b1b43bf4", size = 68550, upload-time = "2026-08-18T05:14:22.343Z" }, ] -[[package]] -name = "importlib-metadata" -version = "8.7.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "zipp" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/f3/49/3b30cad09e7771a4982d9975a8cbf64f00d4a1ececb53297f1d9a7be1b10/importlib_metadata-8.7.1.tar.gz", hash = "sha256:49fef1ae6440c182052f407c8d34a68f72efc36db9ca90dc0113398f2fdde8bb", size = 57107, upload-time = "2025-12-21T10:00:19.278Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fa/5e/f8e9a1d23b9c20a551a8a02ea3637b4642e22c2626e3a13a9a29cdea99eb/importlib_metadata-8.7.1-py3-none-any.whl", hash = "sha256:5a1f80bf1daa489495071efbb095d75a634cf28a8bc299581244063b53176151", size = 27865, upload-time = "2025-12-21T10:00:18.329Z" }, -] - [[package]] name = "jiter" version = "0.13.0" @@ -462,50 +450,80 @@ wheels = [ [[package]] name = "opentelemetry-api" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "importlib-metadata" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2c/1d/4049a9e8698361cc1a1aa03a6c59e4fa4c71e0c0f94a30f988a6876a2ae6/opentelemetry_api-1.40.0.tar.gz", hash = "sha256:159be641c0b04d11e9ecd576906462773eb97ae1b657730f0ecf64d32071569f", size = 70851, upload-time = "2026-03-04T14:17:21.555Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2e/02/6e0ae9cc61bd3169d401077b507b3ebc344745171e1051ab430be012dcd9/opentelemetry_api-1.45.1.tar.gz", hash = "sha256:aa38ed19bcc084ba42782a73255b3582283eced7ad6dddbd6695189e69adfb75", size = 72804, upload-time = "2026-10-06T17:32:58.133Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/41/f7dcf80b81ee8e71c1a2b59f14208bc723edbd89ed027a73b175abf6348e/opentelemetry_api-1.45.1-py3-none-any.whl", hash = "sha256:b31553efa588ae44bc306f863c785c5333a9ecc091248c6ee68b4b6c87fdedfb", size = 60256, upload-time = "2026-10-06T17:32:33.506Z" }, +] + +[[package]] +name = "opentelemetry-exporter-http-transport" +version = "0.66b1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/62/0c/e3ebdb4b507f66afcc905e6885a4946969bd75b45988492643356fbbdc63/opentelemetry_exporter_http_transport-0.66b1.tar.gz", hash = "sha256:443080203bf52586ce0b2ad901e8951c61833eab1aa539ae6f1f16fe9e8e7952", size = 11693, upload-time = "2026-10-06T17:32:59.65Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/69/6af86ff66492b481c6a4c05dcfd68beb47ed8ba046440a26a2aac76b95c7/opentelemetry_exporter_http_transport-0.66b1-py3-none-any.whl", hash = "sha256:2f95404bdee7f9d2d529c7de56c7bd86d014d774d8fbf137810e0167f8a492bf", size = 12155, upload-time = "2026-10-06T17:32:35.454Z" }, +] + +[package.optional-dependencies] +requests = [ + { name = "requests" }, +] + +[[package]] +name = "opentelemetry-exporter-otlp-common" +version = "0.66b1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-sdk" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cb/19/41de712173f43057e4532d42ece7d0c6d4210d353e5752433cb14987643f/opentelemetry_exporter_otlp_common-0.66b1.tar.gz", hash = "sha256:6b1403487a2185ac1feb45fd5546fdf8630ce71c36bcefaadf51e2130e9e23f9", size = 14325, upload-time = "2026-10-06T17:33:01.725Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5f/bf/93795954016c522008da367da292adceed71cca6ee1717e1d64c83089099/opentelemetry_api-1.40.0-py3-none-any.whl", hash = "sha256:82dd69331ae74b06f6a874704be0cfaa49a1650e1537d4a813b86ecef7d0ecf9", size = 68676, upload-time = "2026-03-04T14:17:01.24Z" }, + { url = "https://files.pythonhosted.org/packages/fc/39/8c23d67665c762aa51840fa06f86e902e8f6f1693bc8d7e3d98cd6e2f753/opentelemetry_exporter_otlp_common-0.66b1-py3-none-any.whl", hash = "sha256:00ff8592c3a7cb729ff3fdc7ffa12372c243bdf2163e80c180994d0c7bd83ee9", size = 12385, upload-time = "2026-10-06T17:32:38.177Z" }, ] [[package]] name = "opentelemetry-exporter-otlp-proto-common" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-proto" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/51/bc/1559d46557fe6eca0b46c88d4c2676285f1f3be2e8d06bb5d15fbffc814a/opentelemetry_exporter_otlp_proto_common-1.40.0.tar.gz", hash = "sha256:1cbee86a4064790b362a86601ee7934f368b81cd4cc2f2e163902a6e7818a0fa", size = 20416, upload-time = "2026-03-04T14:17:23.801Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c1/8e/65e85e5137991a3c493b11682151d198638a5bc1dd4b4c5f67e013c57d7c/opentelemetry_exporter_otlp_proto_common-1.45.1.tar.gz", hash = "sha256:2e4adcc3a67bcf57804fc49514f0ef64974ca7590aa3491da389852b4a0628f6", size = 18873, upload-time = "2026-10-06T17:33:04.471Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8b/ca/8f122055c97a932311a3f640273f084e738008933503d0c2563cd5d591fc/opentelemetry_exporter_otlp_proto_common-1.40.0-py3-none-any.whl", hash = "sha256:7081ff453835a82417bf38dccf122c827c3cbc94f2079b03bba02a3165f25149", size = 18369, upload-time = "2026-03-04T14:17:04.796Z" }, + { url = "https://files.pythonhosted.org/packages/84/aa/92f225d353904e7f70b8b3e3c1b02db0cf56f744c2e83c581dc372e78873/opentelemetry_exporter_otlp_proto_common-1.45.1-py3-none-any.whl", hash = "sha256:2f446183ae7047b036226f1d846c41a834b0e8755ad13b51a51dd38952eb466c", size = 15393, upload-time = "2026-10-06T17:32:41.911Z" }, ] [[package]] name = "opentelemetry-exporter-otlp-proto-http" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "googleapis-common-protos" }, { name = "opentelemetry-api" }, + { name = "opentelemetry-exporter-http-transport", extra = ["requests"] }, + { name = "opentelemetry-exporter-otlp-common" }, { name = "opentelemetry-exporter-otlp-proto-common" }, { name = "opentelemetry-proto" }, { name = "opentelemetry-sdk" }, { name = "requests" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2e/fa/73d50e2c15c56be4d000c98e24221d494674b0cc95524e2a8cb3856d95a4/opentelemetry_exporter_otlp_proto_http-1.40.0.tar.gz", hash = "sha256:db48f5e0f33217588bbc00274a31517ba830da576e59503507c839b38fa0869c", size = 17772, upload-time = "2026-03-04T14:17:25.324Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/17/26487707ea4caa97b17e6e4b5fa72133a53512ffa2f5cf7a49ef284b29cb/opentelemetry_exporter_otlp_proto_http-1.45.1.tar.gz", hash = "sha256:45c218405ce3fd879596924b1874bf9a8f6880206d61065c5a912c8e5c297fb7", size = 28839, upload-time = "2026-10-06T17:33:05.713Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/3a/8865d6754e61c9fb170cdd530a124a53769ee5f740236064816eb0ca7301/opentelemetry_exporter_otlp_proto_http-1.40.0-py3-none-any.whl", hash = "sha256:a8d1dab28f504c5d96577d6509f80a8150e44e8f45f82cdbe0e34c99ab040069", size = 19960, upload-time = "2026-03-04T14:17:07.153Z" }, + { url = "https://files.pythonhosted.org/packages/aa/1f/517eaa0187ba106a9da97160ce2add3a371812681dc440930b267f714e42/opentelemetry_exporter_otlp_proto_http-1.45.1-py3-none-any.whl", hash = "sha256:24a97cf3753c7fb52fad44a696e452ff371686339e2acf3309e2eda3d0230700", size = 22180, upload-time = "2026-10-06T17:32:43.946Z" }, ] [[package]] name = "opentelemetry-instrumentation" -version = "0.61b0" +version = "0.66b1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, @@ -513,9 +531,9 @@ dependencies = [ { name = "packaging" }, { name = "wrapt" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/da/37/6bf8e66bfcee5d3c6515b79cb2ee9ad05fe573c20f7ceb288d0e7eeec28c/opentelemetry_instrumentation-0.61b0.tar.gz", hash = "sha256:cb21b48db738c9de196eba6b805b4ff9de3b7f187e4bbf9a466fa170514f1fc7", size = 32606, upload-time = "2026-03-04T14:20:16.825Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a5/03/89e47ff8d52a4f83b343e6eb9ef1698ff45357216e5b6b2b21e0da5c5c7d/opentelemetry_instrumentation-0.66b1.tar.gz", hash = "sha256:e79a510f7d87c72d95e964ddb42193a0d9a75668c027d980eab032ea1322a5ce", size = 43308, upload-time = "2026-10-06T17:36:10.703Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d8/3e/f6f10f178b6316de67f0dfdbbb699a24fbe8917cf1743c1595fb9dcdd461/opentelemetry_instrumentation-0.61b0-py3-none-any.whl", hash = "sha256:92a93a280e69788e8f88391247cc530fd81f16f2b011979d4d6398f805cfbc63", size = 33448, upload-time = "2026-03-04T14:19:02.447Z" }, + { url = "https://files.pythonhosted.org/packages/da/b2/d1413681ff43e13ac9860df27e1226d3199ab0b97b352ceea41abcc660a5/opentelemetry_instrumentation-0.66b1-py3-none-any.whl", hash = "sha256:4c4aa14dc9a24a02325a9d4c42c4d0208dbb1374c2b1b8fe6c9392d59f3e1008", size = 36904, upload-time = "2026-10-06T17:35:11.663Z" }, ] [[package]] @@ -534,41 +552,41 @@ wheels = [ [[package]] name = "opentelemetry-proto" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "protobuf" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4c/77/dd38991db037fdfce45849491cb61de5ab000f49824a00230afb112a4392/opentelemetry_proto-1.40.0.tar.gz", hash = "sha256:03f639ca129ba513f5819810f5b1f42bcb371391405d99c168fe6937c62febcd", size = 45667, upload-time = "2026-03-04T14:17:31.194Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4b/7f/15f014fb195da6c2dbb6c71399b8e76824878718e94de6454038488eed28/opentelemetry_proto-1.45.1.tar.gz", hash = "sha256:79e0fb95e4616691a469439238aa9224d75779b3e108e895d1aa125ab29ca77c", size = 46488, upload-time = "2026-10-06T17:33:11.49Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b9/b2/189b2577dde745b15625b3214302605b1353436219d42b7912e77fa8dc24/opentelemetry_proto-1.40.0-py3-none-any.whl", hash = "sha256:266c4385d88923a23d63e353e9761af0f47a6ed0d486979777fe4de59dc9b25f", size = 72073, upload-time = "2026-03-04T14:17:16.673Z" }, + { url = "https://files.pythonhosted.org/packages/ab/9a/42ec8180a769516ae757e893b69736826efceac7332553915b4528a91c6d/opentelemetry_proto-1.45.1-py3-none-any.whl", hash = "sha256:f38e2a8413053c180cd3d2637fbb279673ec2f6a6e09c995aafa2f452c52b46e", size = 72488, upload-time = "2026-10-06T17:32:53.057Z" }, ] [[package]] name = "opentelemetry-sdk" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, { name = "opentelemetry-semantic-conventions" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/58/fd/3c3125b20ba18ce2155ba9ea74acb0ae5d25f8cd39cfd37455601b7955cc/opentelemetry_sdk-1.40.0.tar.gz", hash = "sha256:18e9f5ec20d859d268c7cb3c5198c8d105d073714db3de50b593b8c1345a48f2", size = 184252, upload-time = "2026-03-04T14:17:31.87Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/79/7392e21a1c8f0c61d90b223e31c7e48cb9d452e91a6b820ad24cca5f23c4/opentelemetry_sdk-1.45.1.tar.gz", hash = "sha256:63d24a6ca645019a631e6a51999c73e93adcac1196ca640b8ae78a7cc4762bf3", size = 218324, upload-time = "2026-10-06T17:33:13.26Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/c5/6a852903d8bfac758c6dc6e9a68b015d3c33f2f1be5e9591e0f4b69c7e0a/opentelemetry_sdk-1.40.0-py3-none-any.whl", hash = "sha256:787d2154a71f4b3d81f20524a8ce061b7db667d24e46753f32a7bc48f1c1f3f1", size = 141951, upload-time = "2026-03-04T14:17:17.961Z" }, + { url = "https://files.pythonhosted.org/packages/95/3c/87c42b4bd6dd297536f04cd9383d212ac557ecd49f2cbdcd46da1c9ef5c8/opentelemetry_sdk-1.45.1-py3-none-any.whl", hash = "sha256:c604c11dc429810812348989115fa44bd558772a3d7442afc43d024f2c250ca4", size = 140063, upload-time = "2026-10-06T17:32:55.04Z" }, ] [[package]] name = "opentelemetry-semantic-conventions" -version = "0.61b0" +version = "0.66b1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6d/c0/4ae7973f3c2cfd2b6e321f1675626f0dab0a97027cc7a297474c9c8f3d04/opentelemetry_semantic_conventions-0.61b0.tar.gz", hash = "sha256:072f65473c5d7c6dc0355b27d6c9d1a679d63b6d4b4b16a9773062cb7e31192a", size = 145755, upload-time = "2026-03-04T14:17:32.664Z" } +sdist = { url = "https://files.pythonhosted.org/packages/46/e4/dbbfb2a010c4db2224a5114638acede6fe563d33cc20fb1752cebcbe6298/opentelemetry_semantic_conventions-0.66b1.tar.gz", hash = "sha256:497ca63bf383723411e8eaf60c8779e9877633c936bb641080adab59d0eb6ec8", size = 150250, upload-time = "2026-10-06T17:33:14.073Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b2/37/cc6a55e448deaa9b27377d087da8615a3416d8ad523d5960b78dbeadd02a/opentelemetry_semantic_conventions-0.61b0-py3-none-any.whl", hash = "sha256:fa530a96be229795f8cef353739b618148b0fe2b4b3f005e60e262926c4d38e2", size = 231621, upload-time = "2026-03-04T14:17:19.33Z" }, + { url = "https://files.pythonhosted.org/packages/bc/14/67f8aa798857f8cf686f515bf93d9bb877ce952ddc8efae0fa25b45ce0d6/opentelemetry_semantic_conventions-0.66b1-py3-none-any.whl", hash = "sha256:d4cddeb4315490b35213f55e2bdc9ac54bb1e4d318927475bed62b35545e581b", size = 206279, upload-time = "2026-10-06T17:32:56.103Z" }, ] [[package]] @@ -676,7 +694,7 @@ wheels = [ [[package]] name = "posthog" -version = "7.75.4" +version = "7.75.11" source = { directory = "../../" } dependencies = [ { name = "backoff" }, @@ -1222,12 +1240,3 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/46/78/10ad9781128ed2f99dbc474f43283b13fea8ba58723e98844367531c18e9/wrapt-1.17.3-cp314-cp314t-win_arm64.whl", hash = "sha256:f38e60678850c42461d4202739f9bf1e3a737c7ad283638251e79cc49effb6b6", size = 38471, upload-time = "2025-08-12T05:52:57.784Z" }, { url = "https://files.pythonhosted.org/packages/1f/f6/a933bd70f98e9cf3e08167fc5cd7aaaca49147e48411c0bd5ae701bb2194/wrapt-1.17.3-py3-none-any.whl", hash = "sha256:7171ae35d2c33d326ac19dd8facb1e82e5fd04ef8c6c0e394d7af55a55051c22", size = 23591, upload-time = "2025-08-12T05:53:20.674Z" }, ] - -[[package]] -name = "zipp" -version = "3.23.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166", size = 25547, upload-time = "2025-06-08T17:06:39.4Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276, upload-time = "2025-06-08T17:06:38.034Z" }, -] diff --git a/examples/example-ai-aws-bedrock/uv.lock b/examples/example-ai-aws-bedrock/uv.lock index 002c854b..3ab92317 100644 --- a/examples/example-ai-aws-bedrock/uv.lock +++ b/examples/example-ai-aws-bedrock/uv.lock @@ -210,72 +210,90 @@ wheels = [ ] [[package]] -name = "importlib-metadata" -version = "8.7.1" +name = "jmespath" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d3/59/322338183ecda247fb5d1763a6cbe46eff7222eaeebafd9fa65d4bf5cb11/jmespath-1.1.0.tar.gz", hash = "sha256:472c87d80f36026ae83c6ddd0f1d05d4e510134ed462851fd5f754c8c3cbb88d", size = 27377, upload-time = "2026-01-22T16:35:26.279Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/14/2f/967ba146e6d58cf6a652da73885f52fc68001525b4197effc174321d70b4/jmespath-1.1.0-py3-none-any.whl", hash = "sha256:a5663118de4908c91729bea0acadca56526eb2698e83de10cd116ae0f4e97c64", size = 20419, upload-time = "2026-01-22T16:35:24.919Z" }, +] + +[[package]] +name = "opentelemetry-api" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "zipp" }, + { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f3/49/3b30cad09e7771a4982d9975a8cbf64f00d4a1ececb53297f1d9a7be1b10/importlib_metadata-8.7.1.tar.gz", hash = "sha256:49fef1ae6440c182052f407c8d34a68f72efc36db9ca90dc0113398f2fdde8bb", size = 57107, upload-time = "2025-12-21T10:00:19.278Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2e/02/6e0ae9cc61bd3169d401077b507b3ebc344745171e1051ab430be012dcd9/opentelemetry_api-1.45.1.tar.gz", hash = "sha256:aa38ed19bcc084ba42782a73255b3582283eced7ad6dddbd6695189e69adfb75", size = 72804, upload-time = "2026-10-06T17:32:58.133Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fa/5e/f8e9a1d23b9c20a551a8a02ea3637b4642e22c2626e3a13a9a29cdea99eb/importlib_metadata-8.7.1-py3-none-any.whl", hash = "sha256:5a1f80bf1daa489495071efbb095d75a634cf28a8bc299581244063b53176151", size = 27865, upload-time = "2025-12-21T10:00:18.329Z" }, + { url = "https://files.pythonhosted.org/packages/1e/41/f7dcf80b81ee8e71c1a2b59f14208bc723edbd89ed027a73b175abf6348e/opentelemetry_api-1.45.1-py3-none-any.whl", hash = "sha256:b31553efa588ae44bc306f863c785c5333a9ecc091248c6ee68b4b6c87fdedfb", size = 60256, upload-time = "2026-10-06T17:32:33.506Z" }, ] [[package]] -name = "jmespath" -version = "1.1.0" +name = "opentelemetry-exporter-http-transport" +version = "0.66b1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d3/59/322338183ecda247fb5d1763a6cbe46eff7222eaeebafd9fa65d4bf5cb11/jmespath-1.1.0.tar.gz", hash = "sha256:472c87d80f36026ae83c6ddd0f1d05d4e510134ed462851fd5f754c8c3cbb88d", size = 27377, upload-time = "2026-01-22T16:35:26.279Z" } +dependencies = [ + { name = "opentelemetry-api" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/62/0c/e3ebdb4b507f66afcc905e6885a4946969bd75b45988492643356fbbdc63/opentelemetry_exporter_http_transport-0.66b1.tar.gz", hash = "sha256:443080203bf52586ce0b2ad901e8951c61833eab1aa539ae6f1f16fe9e8e7952", size = 11693, upload-time = "2026-10-06T17:32:59.65Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/14/2f/967ba146e6d58cf6a652da73885f52fc68001525b4197effc174321d70b4/jmespath-1.1.0-py3-none-any.whl", hash = "sha256:a5663118de4908c91729bea0acadca56526eb2698e83de10cd116ae0f4e97c64", size = 20419, upload-time = "2026-01-22T16:35:24.919Z" }, + { url = "https://files.pythonhosted.org/packages/04/69/6af86ff66492b481c6a4c05dcfd68beb47ed8ba046440a26a2aac76b95c7/opentelemetry_exporter_http_transport-0.66b1-py3-none-any.whl", hash = "sha256:2f95404bdee7f9d2d529c7de56c7bd86d014d774d8fbf137810e0167f8a492bf", size = 12155, upload-time = "2026-10-06T17:32:35.454Z" }, +] + +[package.optional-dependencies] +requests = [ + { name = "requests" }, ] [[package]] -name = "opentelemetry-api" -version = "1.40.0" +name = "opentelemetry-exporter-otlp-common" +version = "0.66b1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "importlib-metadata" }, - { name = "typing-extensions" }, + { name = "opentelemetry-sdk" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2c/1d/4049a9e8698361cc1a1aa03a6c59e4fa4c71e0c0f94a30f988a6876a2ae6/opentelemetry_api-1.40.0.tar.gz", hash = "sha256:159be641c0b04d11e9ecd576906462773eb97ae1b657730f0ecf64d32071569f", size = 70851, upload-time = "2026-03-04T14:17:21.555Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cb/19/41de712173f43057e4532d42ece7d0c6d4210d353e5752433cb14987643f/opentelemetry_exporter_otlp_common-0.66b1.tar.gz", hash = "sha256:6b1403487a2185ac1feb45fd5546fdf8630ce71c36bcefaadf51e2130e9e23f9", size = 14325, upload-time = "2026-10-06T17:33:01.725Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5f/bf/93795954016c522008da367da292adceed71cca6ee1717e1d64c83089099/opentelemetry_api-1.40.0-py3-none-any.whl", hash = "sha256:82dd69331ae74b06f6a874704be0cfaa49a1650e1537d4a813b86ecef7d0ecf9", size = 68676, upload-time = "2026-03-04T14:17:01.24Z" }, + { url = "https://files.pythonhosted.org/packages/fc/39/8c23d67665c762aa51840fa06f86e902e8f6f1693bc8d7e3d98cd6e2f753/opentelemetry_exporter_otlp_common-0.66b1-py3-none-any.whl", hash = "sha256:00ff8592c3a7cb729ff3fdc7ffa12372c243bdf2163e80c180994d0c7bd83ee9", size = 12385, upload-time = "2026-10-06T17:32:38.177Z" }, ] [[package]] name = "opentelemetry-exporter-otlp-proto-common" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-proto" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/51/bc/1559d46557fe6eca0b46c88d4c2676285f1f3be2e8d06bb5d15fbffc814a/opentelemetry_exporter_otlp_proto_common-1.40.0.tar.gz", hash = "sha256:1cbee86a4064790b362a86601ee7934f368b81cd4cc2f2e163902a6e7818a0fa", size = 20416, upload-time = "2026-03-04T14:17:23.801Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c1/8e/65e85e5137991a3c493b11682151d198638a5bc1dd4b4c5f67e013c57d7c/opentelemetry_exporter_otlp_proto_common-1.45.1.tar.gz", hash = "sha256:2e4adcc3a67bcf57804fc49514f0ef64974ca7590aa3491da389852b4a0628f6", size = 18873, upload-time = "2026-10-06T17:33:04.471Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8b/ca/8f122055c97a932311a3f640273f084e738008933503d0c2563cd5d591fc/opentelemetry_exporter_otlp_proto_common-1.40.0-py3-none-any.whl", hash = "sha256:7081ff453835a82417bf38dccf122c827c3cbc94f2079b03bba02a3165f25149", size = 18369, upload-time = "2026-03-04T14:17:04.796Z" }, + { url = "https://files.pythonhosted.org/packages/84/aa/92f225d353904e7f70b8b3e3c1b02db0cf56f744c2e83c581dc372e78873/opentelemetry_exporter_otlp_proto_common-1.45.1-py3-none-any.whl", hash = "sha256:2f446183ae7047b036226f1d846c41a834b0e8755ad13b51a51dd38952eb466c", size = 15393, upload-time = "2026-10-06T17:32:41.911Z" }, ] [[package]] name = "opentelemetry-exporter-otlp-proto-http" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "googleapis-common-protos" }, { name = "opentelemetry-api" }, + { name = "opentelemetry-exporter-http-transport", extra = ["requests"] }, + { name = "opentelemetry-exporter-otlp-common" }, { name = "opentelemetry-exporter-otlp-proto-common" }, { name = "opentelemetry-proto" }, { name = "opentelemetry-sdk" }, { name = "requests" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2e/fa/73d50e2c15c56be4d000c98e24221d494674b0cc95524e2a8cb3856d95a4/opentelemetry_exporter_otlp_proto_http-1.40.0.tar.gz", hash = "sha256:db48f5e0f33217588bbc00274a31517ba830da576e59503507c839b38fa0869c", size = 17772, upload-time = "2026-03-04T14:17:25.324Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/17/26487707ea4caa97b17e6e4b5fa72133a53512ffa2f5cf7a49ef284b29cb/opentelemetry_exporter_otlp_proto_http-1.45.1.tar.gz", hash = "sha256:45c218405ce3fd879596924b1874bf9a8f6880206d61065c5a912c8e5c297fb7", size = 28839, upload-time = "2026-10-06T17:33:05.713Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/3a/8865d6754e61c9fb170cdd530a124a53769ee5f740236064816eb0ca7301/opentelemetry_exporter_otlp_proto_http-1.40.0-py3-none-any.whl", hash = "sha256:a8d1dab28f504c5d96577d6509f80a8150e44e8f45f82cdbe0e34c99ab040069", size = 19960, upload-time = "2026-03-04T14:17:07.153Z" }, + { url = "https://files.pythonhosted.org/packages/aa/1f/517eaa0187ba106a9da97160ce2add3a371812681dc440930b267f714e42/opentelemetry_exporter_otlp_proto_http-1.45.1-py3-none-any.whl", hash = "sha256:24a97cf3753c7fb52fad44a696e452ff371686339e2acf3309e2eda3d0230700", size = 22180, upload-time = "2026-10-06T17:32:43.946Z" }, ] [[package]] name = "opentelemetry-instrumentation" -version = "0.61b0" +version = "0.66b1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, @@ -283,24 +301,25 @@ dependencies = [ { name = "packaging" }, { name = "wrapt" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/da/37/6bf8e66bfcee5d3c6515b79cb2ee9ad05fe573c20f7ceb288d0e7eeec28c/opentelemetry_instrumentation-0.61b0.tar.gz", hash = "sha256:cb21b48db738c9de196eba6b805b4ff9de3b7f187e4bbf9a466fa170514f1fc7", size = 32606, upload-time = "2026-03-04T14:20:16.825Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a5/03/89e47ff8d52a4f83b343e6eb9ef1698ff45357216e5b6b2b21e0da5c5c7d/opentelemetry_instrumentation-0.66b1.tar.gz", hash = "sha256:e79a510f7d87c72d95e964ddb42193a0d9a75668c027d980eab032ea1322a5ce", size = 43308, upload-time = "2026-10-06T17:36:10.703Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d8/3e/f6f10f178b6316de67f0dfdbbb699a24fbe8917cf1743c1595fb9dcdd461/opentelemetry_instrumentation-0.61b0-py3-none-any.whl", hash = "sha256:92a93a280e69788e8f88391247cc530fd81f16f2b011979d4d6398f805cfbc63", size = 33448, upload-time = "2026-03-04T14:19:02.447Z" }, + { url = "https://files.pythonhosted.org/packages/da/b2/d1413681ff43e13ac9860df27e1226d3199ab0b97b352ceea41abcc660a5/opentelemetry_instrumentation-0.66b1-py3-none-any.whl", hash = "sha256:4c4aa14dc9a24a02325a9d4c42c4d0208dbb1374c2b1b8fe6c9392d59f3e1008", size = 36904, upload-time = "2026-10-06T17:35:11.663Z" }, ] [[package]] name = "opentelemetry-instrumentation-botocore" -version = "0.61b0" +version = "0.66b1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, { name = "opentelemetry-instrumentation" }, { name = "opentelemetry-propagator-aws-xray" }, { name = "opentelemetry-semantic-conventions" }, + { name = "wrapt" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/cb/7f/2acb6d4e8cc70726cfbb24885a653ef5fdc3ac2d0a26ca3e6bff58416c2e/opentelemetry_instrumentation_botocore-0.61b0.tar.gz", hash = "sha256:49dee5f48d133b3bfadaa29bcbff28225899dc495ca14a9c1bb60b74fb4cd84d", size = 121236, upload-time = "2026-03-04T14:20:26.426Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4d/f6/f648150c2bf30784b4ff448c15a6b5ad804ac41b91362ebe023ce14b88c3/opentelemetry_instrumentation_botocore-0.66b1.tar.gz", hash = "sha256:c601923e40aa008f4c0466b714c112fe47da07adb72ae6c69e2ac95992bcd66e", size = 129133, upload-time = "2026-10-06T17:36:18.898Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/69/43/0ed1ac34b52a62b3694c22aee5c7a9c5b6120938d927d79de53ad9edb0cf/opentelemetry_instrumentation_botocore-0.61b0-py3-none-any.whl", hash = "sha256:b019d2f60562265319e64a75c1c9e7bad31c00b2a568def3cc5c57eaf9a06057", size = 38359, upload-time = "2026-03-04T14:19:19.028Z" }, + { url = "https://files.pythonhosted.org/packages/5d/6b/1f467c7e5725a8df6c2ed59446c5b9632bc480a525396a962ab67a4d7f28/opentelemetry_instrumentation_botocore-0.66b1-py3-none-any.whl", hash = "sha256:6352ceb4f204b13cd5b448f4df10de4a1a82f345fd0a81f8816a60e9ce3fdfc2", size = 36199, upload-time = "2026-10-06T17:35:23.976Z" }, ] [[package]] @@ -317,41 +336,41 @@ wheels = [ [[package]] name = "opentelemetry-proto" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "protobuf" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4c/77/dd38991db037fdfce45849491cb61de5ab000f49824a00230afb112a4392/opentelemetry_proto-1.40.0.tar.gz", hash = "sha256:03f639ca129ba513f5819810f5b1f42bcb371391405d99c168fe6937c62febcd", size = 45667, upload-time = "2026-03-04T14:17:31.194Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4b/7f/15f014fb195da6c2dbb6c71399b8e76824878718e94de6454038488eed28/opentelemetry_proto-1.45.1.tar.gz", hash = "sha256:79e0fb95e4616691a469439238aa9224d75779b3e108e895d1aa125ab29ca77c", size = 46488, upload-time = "2026-10-06T17:33:11.49Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b9/b2/189b2577dde745b15625b3214302605b1353436219d42b7912e77fa8dc24/opentelemetry_proto-1.40.0-py3-none-any.whl", hash = "sha256:266c4385d88923a23d63e353e9761af0f47a6ed0d486979777fe4de59dc9b25f", size = 72073, upload-time = "2026-03-04T14:17:16.673Z" }, + { url = "https://files.pythonhosted.org/packages/ab/9a/42ec8180a769516ae757e893b69736826efceac7332553915b4528a91c6d/opentelemetry_proto-1.45.1-py3-none-any.whl", hash = "sha256:f38e2a8413053c180cd3d2637fbb279673ec2f6a6e09c995aafa2f452c52b46e", size = 72488, upload-time = "2026-10-06T17:32:53.057Z" }, ] [[package]] name = "opentelemetry-sdk" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, { name = "opentelemetry-semantic-conventions" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/58/fd/3c3125b20ba18ce2155ba9ea74acb0ae5d25f8cd39cfd37455601b7955cc/opentelemetry_sdk-1.40.0.tar.gz", hash = "sha256:18e9f5ec20d859d268c7cb3c5198c8d105d073714db3de50b593b8c1345a48f2", size = 184252, upload-time = "2026-03-04T14:17:31.87Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/79/7392e21a1c8f0c61d90b223e31c7e48cb9d452e91a6b820ad24cca5f23c4/opentelemetry_sdk-1.45.1.tar.gz", hash = "sha256:63d24a6ca645019a631e6a51999c73e93adcac1196ca640b8ae78a7cc4762bf3", size = 218324, upload-time = "2026-10-06T17:33:13.26Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/c5/6a852903d8bfac758c6dc6e9a68b015d3c33f2f1be5e9591e0f4b69c7e0a/opentelemetry_sdk-1.40.0-py3-none-any.whl", hash = "sha256:787d2154a71f4b3d81f20524a8ce061b7db667d24e46753f32a7bc48f1c1f3f1", size = 141951, upload-time = "2026-03-04T14:17:17.961Z" }, + { url = "https://files.pythonhosted.org/packages/95/3c/87c42b4bd6dd297536f04cd9383d212ac557ecd49f2cbdcd46da1c9ef5c8/opentelemetry_sdk-1.45.1-py3-none-any.whl", hash = "sha256:c604c11dc429810812348989115fa44bd558772a3d7442afc43d024f2c250ca4", size = 140063, upload-time = "2026-10-06T17:32:55.04Z" }, ] [[package]] name = "opentelemetry-semantic-conventions" -version = "0.61b0" +version = "0.66b1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6d/c0/4ae7973f3c2cfd2b6e321f1675626f0dab0a97027cc7a297474c9c8f3d04/opentelemetry_semantic_conventions-0.61b0.tar.gz", hash = "sha256:072f65473c5d7c6dc0355b27d6c9d1a679d63b6d4b4b16a9773062cb7e31192a", size = 145755, upload-time = "2026-03-04T14:17:32.664Z" } +sdist = { url = "https://files.pythonhosted.org/packages/46/e4/dbbfb2a010c4db2224a5114638acede6fe563d33cc20fb1752cebcbe6298/opentelemetry_semantic_conventions-0.66b1.tar.gz", hash = "sha256:497ca63bf383723411e8eaf60c8779e9877633c936bb641080adab59d0eb6ec8", size = 150250, upload-time = "2026-10-06T17:33:14.073Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b2/37/cc6a55e448deaa9b27377d087da8615a3416d8ad523d5960b78dbeadd02a/opentelemetry_semantic_conventions-0.61b0-py3-none-any.whl", hash = "sha256:fa530a96be229795f8cef353739b618148b0fe2b4b3f005e60e262926c4d38e2", size = 231621, upload-time = "2026-03-04T14:17:19.33Z" }, + { url = "https://files.pythonhosted.org/packages/bc/14/67f8aa798857f8cf686f515bf93d9bb877ce952ddc8efae0fa25b45ce0d6/opentelemetry_semantic_conventions-0.66b1-py3-none-any.whl", hash = "sha256:d4cddeb4315490b35213f55e2bdc9ac54bb1e4d318927475bed62b35545e581b", size = 206279, upload-time = "2026-10-06T17:32:56.103Z" }, ] [[package]] @@ -365,7 +384,7 @@ wheels = [ [[package]] name = "posthog" -version = "7.75.4" +version = "7.75.11" source = { directory = "../../" } dependencies = [ { name = "backoff" }, @@ -597,12 +616,3 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/46/78/10ad9781128ed2f99dbc474f43283b13fea8ba58723e98844367531c18e9/wrapt-1.17.3-cp314-cp314t-win_arm64.whl", hash = "sha256:f38e60678850c42461d4202739f9bf1e3a737c7ad283638251e79cc49effb6b6", size = 38471, upload-time = "2025-08-12T05:52:57.784Z" }, { url = "https://files.pythonhosted.org/packages/1f/f6/a933bd70f98e9cf3e08167fc5cd7aaaca49147e48411c0bd5ae701bb2194/wrapt-1.17.3-py3-none-any.whl", hash = "sha256:7171ae35d2c33d326ac19dd8facb1e82e5fd04ef8c6c0e394d7af55a55051c22", size = 23591, upload-time = "2025-08-12T05:53:20.674Z" }, ] - -[[package]] -name = "zipp" -version = "3.23.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166", size = 25547, upload-time = "2025-06-08T17:06:39.4Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276, upload-time = "2025-06-08T17:06:38.034Z" }, -] diff --git a/examples/example-ai-azure-openai/uv.lock b/examples/example-ai-azure-openai/uv.lock index 887d6748..a5ef6855 100644 --- a/examples/example-ai-azure-openai/uv.lock +++ b/examples/example-ai-azure-openai/uv.lock @@ -262,18 +262,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/57/b0/0e52c878c53f245edd3a11020f20979b3f490f245af532c7cae3027754b5/idna-3.19-py3-none-any.whl", hash = "sha256:815e7be7a7806d54abb586dc943addc79e8b2ee16915059658cbeff4b1b43bf4", size = 68550, upload-time = "2026-08-18T05:14:22.343Z" }, ] -[[package]] -name = "importlib-metadata" -version = "8.7.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "zipp" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/f3/49/3b30cad09e7771a4982d9975a8cbf64f00d4a1ececb53297f1d9a7be1b10/importlib_metadata-8.7.1.tar.gz", hash = "sha256:49fef1ae6440c182052f407c8d34a68f72efc36db9ca90dc0113398f2fdde8bb", size = 57107, upload-time = "2025-12-21T10:00:19.278Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fa/5e/f8e9a1d23b9c20a551a8a02ea3637b4642e22c2626e3a13a9a29cdea99eb/importlib_metadata-8.7.1-py3-none-any.whl", hash = "sha256:5a1f80bf1daa489495071efbb095d75a634cf28a8bc299581244063b53176151", size = 27865, upload-time = "2025-12-21T10:00:18.329Z" }, -] - [[package]] name = "jiter" version = "0.13.0" @@ -392,50 +380,80 @@ wheels = [ [[package]] name = "opentelemetry-api" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "importlib-metadata" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2c/1d/4049a9e8698361cc1a1aa03a6c59e4fa4c71e0c0f94a30f988a6876a2ae6/opentelemetry_api-1.40.0.tar.gz", hash = "sha256:159be641c0b04d11e9ecd576906462773eb97ae1b657730f0ecf64d32071569f", size = 70851, upload-time = "2026-03-04T14:17:21.555Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2e/02/6e0ae9cc61bd3169d401077b507b3ebc344745171e1051ab430be012dcd9/opentelemetry_api-1.45.1.tar.gz", hash = "sha256:aa38ed19bcc084ba42782a73255b3582283eced7ad6dddbd6695189e69adfb75", size = 72804, upload-time = "2026-10-06T17:32:58.133Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/41/f7dcf80b81ee8e71c1a2b59f14208bc723edbd89ed027a73b175abf6348e/opentelemetry_api-1.45.1-py3-none-any.whl", hash = "sha256:b31553efa588ae44bc306f863c785c5333a9ecc091248c6ee68b4b6c87fdedfb", size = 60256, upload-time = "2026-10-06T17:32:33.506Z" }, +] + +[[package]] +name = "opentelemetry-exporter-http-transport" +version = "0.66b1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/62/0c/e3ebdb4b507f66afcc905e6885a4946969bd75b45988492643356fbbdc63/opentelemetry_exporter_http_transport-0.66b1.tar.gz", hash = "sha256:443080203bf52586ce0b2ad901e8951c61833eab1aa539ae6f1f16fe9e8e7952", size = 11693, upload-time = "2026-10-06T17:32:59.65Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/69/6af86ff66492b481c6a4c05dcfd68beb47ed8ba046440a26a2aac76b95c7/opentelemetry_exporter_http_transport-0.66b1-py3-none-any.whl", hash = "sha256:2f95404bdee7f9d2d529c7de56c7bd86d014d774d8fbf137810e0167f8a492bf", size = 12155, upload-time = "2026-10-06T17:32:35.454Z" }, +] + +[package.optional-dependencies] +requests = [ + { name = "requests" }, +] + +[[package]] +name = "opentelemetry-exporter-otlp-common" +version = "0.66b1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-sdk" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cb/19/41de712173f43057e4532d42ece7d0c6d4210d353e5752433cb14987643f/opentelemetry_exporter_otlp_common-0.66b1.tar.gz", hash = "sha256:6b1403487a2185ac1feb45fd5546fdf8630ce71c36bcefaadf51e2130e9e23f9", size = 14325, upload-time = "2026-10-06T17:33:01.725Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5f/bf/93795954016c522008da367da292adceed71cca6ee1717e1d64c83089099/opentelemetry_api-1.40.0-py3-none-any.whl", hash = "sha256:82dd69331ae74b06f6a874704be0cfaa49a1650e1537d4a813b86ecef7d0ecf9", size = 68676, upload-time = "2026-03-04T14:17:01.24Z" }, + { url = "https://files.pythonhosted.org/packages/fc/39/8c23d67665c762aa51840fa06f86e902e8f6f1693bc8d7e3d98cd6e2f753/opentelemetry_exporter_otlp_common-0.66b1-py3-none-any.whl", hash = "sha256:00ff8592c3a7cb729ff3fdc7ffa12372c243bdf2163e80c180994d0c7bd83ee9", size = 12385, upload-time = "2026-10-06T17:32:38.177Z" }, ] [[package]] name = "opentelemetry-exporter-otlp-proto-common" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-proto" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/51/bc/1559d46557fe6eca0b46c88d4c2676285f1f3be2e8d06bb5d15fbffc814a/opentelemetry_exporter_otlp_proto_common-1.40.0.tar.gz", hash = "sha256:1cbee86a4064790b362a86601ee7934f368b81cd4cc2f2e163902a6e7818a0fa", size = 20416, upload-time = "2026-03-04T14:17:23.801Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c1/8e/65e85e5137991a3c493b11682151d198638a5bc1dd4b4c5f67e013c57d7c/opentelemetry_exporter_otlp_proto_common-1.45.1.tar.gz", hash = "sha256:2e4adcc3a67bcf57804fc49514f0ef64974ca7590aa3491da389852b4a0628f6", size = 18873, upload-time = "2026-10-06T17:33:04.471Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8b/ca/8f122055c97a932311a3f640273f084e738008933503d0c2563cd5d591fc/opentelemetry_exporter_otlp_proto_common-1.40.0-py3-none-any.whl", hash = "sha256:7081ff453835a82417bf38dccf122c827c3cbc94f2079b03bba02a3165f25149", size = 18369, upload-time = "2026-03-04T14:17:04.796Z" }, + { url = "https://files.pythonhosted.org/packages/84/aa/92f225d353904e7f70b8b3e3c1b02db0cf56f744c2e83c581dc372e78873/opentelemetry_exporter_otlp_proto_common-1.45.1-py3-none-any.whl", hash = "sha256:2f446183ae7047b036226f1d846c41a834b0e8755ad13b51a51dd38952eb466c", size = 15393, upload-time = "2026-10-06T17:32:41.911Z" }, ] [[package]] name = "opentelemetry-exporter-otlp-proto-http" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "googleapis-common-protos" }, { name = "opentelemetry-api" }, + { name = "opentelemetry-exporter-http-transport", extra = ["requests"] }, + { name = "opentelemetry-exporter-otlp-common" }, { name = "opentelemetry-exporter-otlp-proto-common" }, { name = "opentelemetry-proto" }, { name = "opentelemetry-sdk" }, { name = "requests" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2e/fa/73d50e2c15c56be4d000c98e24221d494674b0cc95524e2a8cb3856d95a4/opentelemetry_exporter_otlp_proto_http-1.40.0.tar.gz", hash = "sha256:db48f5e0f33217588bbc00274a31517ba830da576e59503507c839b38fa0869c", size = 17772, upload-time = "2026-03-04T14:17:25.324Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/17/26487707ea4caa97b17e6e4b5fa72133a53512ffa2f5cf7a49ef284b29cb/opentelemetry_exporter_otlp_proto_http-1.45.1.tar.gz", hash = "sha256:45c218405ce3fd879596924b1874bf9a8f6880206d61065c5a912c8e5c297fb7", size = 28839, upload-time = "2026-10-06T17:33:05.713Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/3a/8865d6754e61c9fb170cdd530a124a53769ee5f740236064816eb0ca7301/opentelemetry_exporter_otlp_proto_http-1.40.0-py3-none-any.whl", hash = "sha256:a8d1dab28f504c5d96577d6509f80a8150e44e8f45f82cdbe0e34c99ab040069", size = 19960, upload-time = "2026-03-04T14:17:07.153Z" }, + { url = "https://files.pythonhosted.org/packages/aa/1f/517eaa0187ba106a9da97160ce2add3a371812681dc440930b267f714e42/opentelemetry_exporter_otlp_proto_http-1.45.1-py3-none-any.whl", hash = "sha256:24a97cf3753c7fb52fad44a696e452ff371686339e2acf3309e2eda3d0230700", size = 22180, upload-time = "2026-10-06T17:32:43.946Z" }, ] [[package]] name = "opentelemetry-instrumentation" -version = "0.61b0" +version = "0.66b1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, @@ -443,9 +461,9 @@ dependencies = [ { name = "packaging" }, { name = "wrapt" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/da/37/6bf8e66bfcee5d3c6515b79cb2ee9ad05fe573c20f7ceb288d0e7eeec28c/opentelemetry_instrumentation-0.61b0.tar.gz", hash = "sha256:cb21b48db738c9de196eba6b805b4ff9de3b7f187e4bbf9a466fa170514f1fc7", size = 32606, upload-time = "2026-03-04T14:20:16.825Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a5/03/89e47ff8d52a4f83b343e6eb9ef1698ff45357216e5b6b2b21e0da5c5c7d/opentelemetry_instrumentation-0.66b1.tar.gz", hash = "sha256:e79a510f7d87c72d95e964ddb42193a0d9a75668c027d980eab032ea1322a5ce", size = 43308, upload-time = "2026-10-06T17:36:10.703Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d8/3e/f6f10f178b6316de67f0dfdbbb699a24fbe8917cf1743c1595fb9dcdd461/opentelemetry_instrumentation-0.61b0-py3-none-any.whl", hash = "sha256:92a93a280e69788e8f88391247cc530fd81f16f2b011979d4d6398f805cfbc63", size = 33448, upload-time = "2026-03-04T14:19:02.447Z" }, + { url = "https://files.pythonhosted.org/packages/da/b2/d1413681ff43e13ac9860df27e1226d3199ab0b97b352ceea41abcc660a5/opentelemetry_instrumentation-0.66b1-py3-none-any.whl", hash = "sha256:4c4aa14dc9a24a02325a9d4c42c4d0208dbb1374c2b1b8fe6c9392d59f3e1008", size = 36904, upload-time = "2026-10-06T17:35:11.663Z" }, ] [[package]] @@ -464,41 +482,41 @@ wheels = [ [[package]] name = "opentelemetry-proto" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "protobuf" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4c/77/dd38991db037fdfce45849491cb61de5ab000f49824a00230afb112a4392/opentelemetry_proto-1.40.0.tar.gz", hash = "sha256:03f639ca129ba513f5819810f5b1f42bcb371391405d99c168fe6937c62febcd", size = 45667, upload-time = "2026-03-04T14:17:31.194Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4b/7f/15f014fb195da6c2dbb6c71399b8e76824878718e94de6454038488eed28/opentelemetry_proto-1.45.1.tar.gz", hash = "sha256:79e0fb95e4616691a469439238aa9224d75779b3e108e895d1aa125ab29ca77c", size = 46488, upload-time = "2026-10-06T17:33:11.49Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b9/b2/189b2577dde745b15625b3214302605b1353436219d42b7912e77fa8dc24/opentelemetry_proto-1.40.0-py3-none-any.whl", hash = "sha256:266c4385d88923a23d63e353e9761af0f47a6ed0d486979777fe4de59dc9b25f", size = 72073, upload-time = "2026-03-04T14:17:16.673Z" }, + { url = "https://files.pythonhosted.org/packages/ab/9a/42ec8180a769516ae757e893b69736826efceac7332553915b4528a91c6d/opentelemetry_proto-1.45.1-py3-none-any.whl", hash = "sha256:f38e2a8413053c180cd3d2637fbb279673ec2f6a6e09c995aafa2f452c52b46e", size = 72488, upload-time = "2026-10-06T17:32:53.057Z" }, ] [[package]] name = "opentelemetry-sdk" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, { name = "opentelemetry-semantic-conventions" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/58/fd/3c3125b20ba18ce2155ba9ea74acb0ae5d25f8cd39cfd37455601b7955cc/opentelemetry_sdk-1.40.0.tar.gz", hash = "sha256:18e9f5ec20d859d268c7cb3c5198c8d105d073714db3de50b593b8c1345a48f2", size = 184252, upload-time = "2026-03-04T14:17:31.87Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/79/7392e21a1c8f0c61d90b223e31c7e48cb9d452e91a6b820ad24cca5f23c4/opentelemetry_sdk-1.45.1.tar.gz", hash = "sha256:63d24a6ca645019a631e6a51999c73e93adcac1196ca640b8ae78a7cc4762bf3", size = 218324, upload-time = "2026-10-06T17:33:13.26Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/c5/6a852903d8bfac758c6dc6e9a68b015d3c33f2f1be5e9591e0f4b69c7e0a/opentelemetry_sdk-1.40.0-py3-none-any.whl", hash = "sha256:787d2154a71f4b3d81f20524a8ce061b7db667d24e46753f32a7bc48f1c1f3f1", size = 141951, upload-time = "2026-03-04T14:17:17.961Z" }, + { url = "https://files.pythonhosted.org/packages/95/3c/87c42b4bd6dd297536f04cd9383d212ac557ecd49f2cbdcd46da1c9ef5c8/opentelemetry_sdk-1.45.1-py3-none-any.whl", hash = "sha256:c604c11dc429810812348989115fa44bd558772a3d7442afc43d024f2c250ca4", size = 140063, upload-time = "2026-10-06T17:32:55.04Z" }, ] [[package]] name = "opentelemetry-semantic-conventions" -version = "0.61b0" +version = "0.66b1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6d/c0/4ae7973f3c2cfd2b6e321f1675626f0dab0a97027cc7a297474c9c8f3d04/opentelemetry_semantic_conventions-0.61b0.tar.gz", hash = "sha256:072f65473c5d7c6dc0355b27d6c9d1a679d63b6d4b4b16a9773062cb7e31192a", size = 145755, upload-time = "2026-03-04T14:17:32.664Z" } +sdist = { url = "https://files.pythonhosted.org/packages/46/e4/dbbfb2a010c4db2224a5114638acede6fe563d33cc20fb1752cebcbe6298/opentelemetry_semantic_conventions-0.66b1.tar.gz", hash = "sha256:497ca63bf383723411e8eaf60c8779e9877633c936bb641080adab59d0eb6ec8", size = 150250, upload-time = "2026-10-06T17:33:14.073Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b2/37/cc6a55e448deaa9b27377d087da8615a3416d8ad523d5960b78dbeadd02a/opentelemetry_semantic_conventions-0.61b0-py3-none-any.whl", hash = "sha256:fa530a96be229795f8cef353739b618148b0fe2b4b3f005e60e262926c4d38e2", size = 231621, upload-time = "2026-03-04T14:17:19.33Z" }, + { url = "https://files.pythonhosted.org/packages/bc/14/67f8aa798857f8cf686f515bf93d9bb877ce952ddc8efae0fa25b45ce0d6/opentelemetry_semantic_conventions-0.66b1-py3-none-any.whl", hash = "sha256:d4cddeb4315490b35213f55e2bdc9ac54bb1e4d318927475bed62b35545e581b", size = 206279, upload-time = "2026-10-06T17:32:56.103Z" }, ] [[package]] @@ -512,7 +530,7 @@ wheels = [ [[package]] name = "posthog" -version = "7.75.4" +version = "7.75.11" source = { directory = "../../" } dependencies = [ { name = "backoff" }, @@ -877,12 +895,3 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/46/78/10ad9781128ed2f99dbc474f43283b13fea8ba58723e98844367531c18e9/wrapt-1.17.3-cp314-cp314t-win_arm64.whl", hash = "sha256:f38e60678850c42461d4202739f9bf1e3a737c7ad283638251e79cc49effb6b6", size = 38471, upload-time = "2025-08-12T05:52:57.784Z" }, { url = "https://files.pythonhosted.org/packages/1f/f6/a933bd70f98e9cf3e08167fc5cd7aaaca49147e48411c0bd5ae701bb2194/wrapt-1.17.3-py3-none-any.whl", hash = "sha256:7171ae35d2c33d326ac19dd8facb1e82e5fd04ef8c6c0e394d7af55a55051c22", size = 23591, upload-time = "2025-08-12T05:53:20.674Z" }, ] - -[[package]] -name = "zipp" -version = "3.23.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166", size = 25547, upload-time = "2025-06-08T17:06:39.4Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276, upload-time = "2025-06-08T17:06:38.034Z" }, -] diff --git a/examples/example-ai-cerebras/uv.lock b/examples/example-ai-cerebras/uv.lock index ef3100dc..c93a3add 100644 --- a/examples/example-ai-cerebras/uv.lock +++ b/examples/example-ai-cerebras/uv.lock @@ -262,18 +262,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/57/b0/0e52c878c53f245edd3a11020f20979b3f490f245af532c7cae3027754b5/idna-3.19-py3-none-any.whl", hash = "sha256:815e7be7a7806d54abb586dc943addc79e8b2ee16915059658cbeff4b1b43bf4", size = 68550, upload-time = "2026-08-18T05:14:22.343Z" }, ] -[[package]] -name = "importlib-metadata" -version = "8.7.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "zipp" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/f3/49/3b30cad09e7771a4982d9975a8cbf64f00d4a1ececb53297f1d9a7be1b10/importlib_metadata-8.7.1.tar.gz", hash = "sha256:49fef1ae6440c182052f407c8d34a68f72efc36db9ca90dc0113398f2fdde8bb", size = 57107, upload-time = "2025-12-21T10:00:19.278Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fa/5e/f8e9a1d23b9c20a551a8a02ea3637b4642e22c2626e3a13a9a29cdea99eb/importlib_metadata-8.7.1-py3-none-any.whl", hash = "sha256:5a1f80bf1daa489495071efbb095d75a634cf28a8bc299581244063b53176151", size = 27865, upload-time = "2025-12-21T10:00:18.329Z" }, -] - [[package]] name = "jiter" version = "0.13.0" @@ -392,50 +380,80 @@ wheels = [ [[package]] name = "opentelemetry-api" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "importlib-metadata" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2c/1d/4049a9e8698361cc1a1aa03a6c59e4fa4c71e0c0f94a30f988a6876a2ae6/opentelemetry_api-1.40.0.tar.gz", hash = "sha256:159be641c0b04d11e9ecd576906462773eb97ae1b657730f0ecf64d32071569f", size = 70851, upload-time = "2026-03-04T14:17:21.555Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2e/02/6e0ae9cc61bd3169d401077b507b3ebc344745171e1051ab430be012dcd9/opentelemetry_api-1.45.1.tar.gz", hash = "sha256:aa38ed19bcc084ba42782a73255b3582283eced7ad6dddbd6695189e69adfb75", size = 72804, upload-time = "2026-10-06T17:32:58.133Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/41/f7dcf80b81ee8e71c1a2b59f14208bc723edbd89ed027a73b175abf6348e/opentelemetry_api-1.45.1-py3-none-any.whl", hash = "sha256:b31553efa588ae44bc306f863c785c5333a9ecc091248c6ee68b4b6c87fdedfb", size = 60256, upload-time = "2026-10-06T17:32:33.506Z" }, +] + +[[package]] +name = "opentelemetry-exporter-http-transport" +version = "0.66b1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/62/0c/e3ebdb4b507f66afcc905e6885a4946969bd75b45988492643356fbbdc63/opentelemetry_exporter_http_transport-0.66b1.tar.gz", hash = "sha256:443080203bf52586ce0b2ad901e8951c61833eab1aa539ae6f1f16fe9e8e7952", size = 11693, upload-time = "2026-10-06T17:32:59.65Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/69/6af86ff66492b481c6a4c05dcfd68beb47ed8ba046440a26a2aac76b95c7/opentelemetry_exporter_http_transport-0.66b1-py3-none-any.whl", hash = "sha256:2f95404bdee7f9d2d529c7de56c7bd86d014d774d8fbf137810e0167f8a492bf", size = 12155, upload-time = "2026-10-06T17:32:35.454Z" }, +] + +[package.optional-dependencies] +requests = [ + { name = "requests" }, +] + +[[package]] +name = "opentelemetry-exporter-otlp-common" +version = "0.66b1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-sdk" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cb/19/41de712173f43057e4532d42ece7d0c6d4210d353e5752433cb14987643f/opentelemetry_exporter_otlp_common-0.66b1.tar.gz", hash = "sha256:6b1403487a2185ac1feb45fd5546fdf8630ce71c36bcefaadf51e2130e9e23f9", size = 14325, upload-time = "2026-10-06T17:33:01.725Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5f/bf/93795954016c522008da367da292adceed71cca6ee1717e1d64c83089099/opentelemetry_api-1.40.0-py3-none-any.whl", hash = "sha256:82dd69331ae74b06f6a874704be0cfaa49a1650e1537d4a813b86ecef7d0ecf9", size = 68676, upload-time = "2026-03-04T14:17:01.24Z" }, + { url = "https://files.pythonhosted.org/packages/fc/39/8c23d67665c762aa51840fa06f86e902e8f6f1693bc8d7e3d98cd6e2f753/opentelemetry_exporter_otlp_common-0.66b1-py3-none-any.whl", hash = "sha256:00ff8592c3a7cb729ff3fdc7ffa12372c243bdf2163e80c180994d0c7bd83ee9", size = 12385, upload-time = "2026-10-06T17:32:38.177Z" }, ] [[package]] name = "opentelemetry-exporter-otlp-proto-common" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-proto" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/51/bc/1559d46557fe6eca0b46c88d4c2676285f1f3be2e8d06bb5d15fbffc814a/opentelemetry_exporter_otlp_proto_common-1.40.0.tar.gz", hash = "sha256:1cbee86a4064790b362a86601ee7934f368b81cd4cc2f2e163902a6e7818a0fa", size = 20416, upload-time = "2026-03-04T14:17:23.801Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c1/8e/65e85e5137991a3c493b11682151d198638a5bc1dd4b4c5f67e013c57d7c/opentelemetry_exporter_otlp_proto_common-1.45.1.tar.gz", hash = "sha256:2e4adcc3a67bcf57804fc49514f0ef64974ca7590aa3491da389852b4a0628f6", size = 18873, upload-time = "2026-10-06T17:33:04.471Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8b/ca/8f122055c97a932311a3f640273f084e738008933503d0c2563cd5d591fc/opentelemetry_exporter_otlp_proto_common-1.40.0-py3-none-any.whl", hash = "sha256:7081ff453835a82417bf38dccf122c827c3cbc94f2079b03bba02a3165f25149", size = 18369, upload-time = "2026-03-04T14:17:04.796Z" }, + { url = "https://files.pythonhosted.org/packages/84/aa/92f225d353904e7f70b8b3e3c1b02db0cf56f744c2e83c581dc372e78873/opentelemetry_exporter_otlp_proto_common-1.45.1-py3-none-any.whl", hash = "sha256:2f446183ae7047b036226f1d846c41a834b0e8755ad13b51a51dd38952eb466c", size = 15393, upload-time = "2026-10-06T17:32:41.911Z" }, ] [[package]] name = "opentelemetry-exporter-otlp-proto-http" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "googleapis-common-protos" }, { name = "opentelemetry-api" }, + { name = "opentelemetry-exporter-http-transport", extra = ["requests"] }, + { name = "opentelemetry-exporter-otlp-common" }, { name = "opentelemetry-exporter-otlp-proto-common" }, { name = "opentelemetry-proto" }, { name = "opentelemetry-sdk" }, { name = "requests" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2e/fa/73d50e2c15c56be4d000c98e24221d494674b0cc95524e2a8cb3856d95a4/opentelemetry_exporter_otlp_proto_http-1.40.0.tar.gz", hash = "sha256:db48f5e0f33217588bbc00274a31517ba830da576e59503507c839b38fa0869c", size = 17772, upload-time = "2026-03-04T14:17:25.324Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/17/26487707ea4caa97b17e6e4b5fa72133a53512ffa2f5cf7a49ef284b29cb/opentelemetry_exporter_otlp_proto_http-1.45.1.tar.gz", hash = "sha256:45c218405ce3fd879596924b1874bf9a8f6880206d61065c5a912c8e5c297fb7", size = 28839, upload-time = "2026-10-06T17:33:05.713Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/3a/8865d6754e61c9fb170cdd530a124a53769ee5f740236064816eb0ca7301/opentelemetry_exporter_otlp_proto_http-1.40.0-py3-none-any.whl", hash = "sha256:a8d1dab28f504c5d96577d6509f80a8150e44e8f45f82cdbe0e34c99ab040069", size = 19960, upload-time = "2026-03-04T14:17:07.153Z" }, + { url = "https://files.pythonhosted.org/packages/aa/1f/517eaa0187ba106a9da97160ce2add3a371812681dc440930b267f714e42/opentelemetry_exporter_otlp_proto_http-1.45.1-py3-none-any.whl", hash = "sha256:24a97cf3753c7fb52fad44a696e452ff371686339e2acf3309e2eda3d0230700", size = 22180, upload-time = "2026-10-06T17:32:43.946Z" }, ] [[package]] name = "opentelemetry-instrumentation" -version = "0.61b0" +version = "0.66b1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, @@ -443,9 +461,9 @@ dependencies = [ { name = "packaging" }, { name = "wrapt" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/da/37/6bf8e66bfcee5d3c6515b79cb2ee9ad05fe573c20f7ceb288d0e7eeec28c/opentelemetry_instrumentation-0.61b0.tar.gz", hash = "sha256:cb21b48db738c9de196eba6b805b4ff9de3b7f187e4bbf9a466fa170514f1fc7", size = 32606, upload-time = "2026-03-04T14:20:16.825Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a5/03/89e47ff8d52a4f83b343e6eb9ef1698ff45357216e5b6b2b21e0da5c5c7d/opentelemetry_instrumentation-0.66b1.tar.gz", hash = "sha256:e79a510f7d87c72d95e964ddb42193a0d9a75668c027d980eab032ea1322a5ce", size = 43308, upload-time = "2026-10-06T17:36:10.703Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d8/3e/f6f10f178b6316de67f0dfdbbb699a24fbe8917cf1743c1595fb9dcdd461/opentelemetry_instrumentation-0.61b0-py3-none-any.whl", hash = "sha256:92a93a280e69788e8f88391247cc530fd81f16f2b011979d4d6398f805cfbc63", size = 33448, upload-time = "2026-03-04T14:19:02.447Z" }, + { url = "https://files.pythonhosted.org/packages/da/b2/d1413681ff43e13ac9860df27e1226d3199ab0b97b352ceea41abcc660a5/opentelemetry_instrumentation-0.66b1-py3-none-any.whl", hash = "sha256:4c4aa14dc9a24a02325a9d4c42c4d0208dbb1374c2b1b8fe6c9392d59f3e1008", size = 36904, upload-time = "2026-10-06T17:35:11.663Z" }, ] [[package]] @@ -464,41 +482,41 @@ wheels = [ [[package]] name = "opentelemetry-proto" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "protobuf" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4c/77/dd38991db037fdfce45849491cb61de5ab000f49824a00230afb112a4392/opentelemetry_proto-1.40.0.tar.gz", hash = "sha256:03f639ca129ba513f5819810f5b1f42bcb371391405d99c168fe6937c62febcd", size = 45667, upload-time = "2026-03-04T14:17:31.194Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4b/7f/15f014fb195da6c2dbb6c71399b8e76824878718e94de6454038488eed28/opentelemetry_proto-1.45.1.tar.gz", hash = "sha256:79e0fb95e4616691a469439238aa9224d75779b3e108e895d1aa125ab29ca77c", size = 46488, upload-time = "2026-10-06T17:33:11.49Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b9/b2/189b2577dde745b15625b3214302605b1353436219d42b7912e77fa8dc24/opentelemetry_proto-1.40.0-py3-none-any.whl", hash = "sha256:266c4385d88923a23d63e353e9761af0f47a6ed0d486979777fe4de59dc9b25f", size = 72073, upload-time = "2026-03-04T14:17:16.673Z" }, + { url = "https://files.pythonhosted.org/packages/ab/9a/42ec8180a769516ae757e893b69736826efceac7332553915b4528a91c6d/opentelemetry_proto-1.45.1-py3-none-any.whl", hash = "sha256:f38e2a8413053c180cd3d2637fbb279673ec2f6a6e09c995aafa2f452c52b46e", size = 72488, upload-time = "2026-10-06T17:32:53.057Z" }, ] [[package]] name = "opentelemetry-sdk" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, { name = "opentelemetry-semantic-conventions" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/58/fd/3c3125b20ba18ce2155ba9ea74acb0ae5d25f8cd39cfd37455601b7955cc/opentelemetry_sdk-1.40.0.tar.gz", hash = "sha256:18e9f5ec20d859d268c7cb3c5198c8d105d073714db3de50b593b8c1345a48f2", size = 184252, upload-time = "2026-03-04T14:17:31.87Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/79/7392e21a1c8f0c61d90b223e31c7e48cb9d452e91a6b820ad24cca5f23c4/opentelemetry_sdk-1.45.1.tar.gz", hash = "sha256:63d24a6ca645019a631e6a51999c73e93adcac1196ca640b8ae78a7cc4762bf3", size = 218324, upload-time = "2026-10-06T17:33:13.26Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/c5/6a852903d8bfac758c6dc6e9a68b015d3c33f2f1be5e9591e0f4b69c7e0a/opentelemetry_sdk-1.40.0-py3-none-any.whl", hash = "sha256:787d2154a71f4b3d81f20524a8ce061b7db667d24e46753f32a7bc48f1c1f3f1", size = 141951, upload-time = "2026-03-04T14:17:17.961Z" }, + { url = "https://files.pythonhosted.org/packages/95/3c/87c42b4bd6dd297536f04cd9383d212ac557ecd49f2cbdcd46da1c9ef5c8/opentelemetry_sdk-1.45.1-py3-none-any.whl", hash = "sha256:c604c11dc429810812348989115fa44bd558772a3d7442afc43d024f2c250ca4", size = 140063, upload-time = "2026-10-06T17:32:55.04Z" }, ] [[package]] name = "opentelemetry-semantic-conventions" -version = "0.61b0" +version = "0.66b1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6d/c0/4ae7973f3c2cfd2b6e321f1675626f0dab0a97027cc7a297474c9c8f3d04/opentelemetry_semantic_conventions-0.61b0.tar.gz", hash = "sha256:072f65473c5d7c6dc0355b27d6c9d1a679d63b6d4b4b16a9773062cb7e31192a", size = 145755, upload-time = "2026-03-04T14:17:32.664Z" } +sdist = { url = "https://files.pythonhosted.org/packages/46/e4/dbbfb2a010c4db2224a5114638acede6fe563d33cc20fb1752cebcbe6298/opentelemetry_semantic_conventions-0.66b1.tar.gz", hash = "sha256:497ca63bf383723411e8eaf60c8779e9877633c936bb641080adab59d0eb6ec8", size = 150250, upload-time = "2026-10-06T17:33:14.073Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b2/37/cc6a55e448deaa9b27377d087da8615a3416d8ad523d5960b78dbeadd02a/opentelemetry_semantic_conventions-0.61b0-py3-none-any.whl", hash = "sha256:fa530a96be229795f8cef353739b618148b0fe2b4b3f005e60e262926c4d38e2", size = 231621, upload-time = "2026-03-04T14:17:19.33Z" }, + { url = "https://files.pythonhosted.org/packages/bc/14/67f8aa798857f8cf686f515bf93d9bb877ce952ddc8efae0fa25b45ce0d6/opentelemetry_semantic_conventions-0.66b1-py3-none-any.whl", hash = "sha256:d4cddeb4315490b35213f55e2bdc9ac54bb1e4d318927475bed62b35545e581b", size = 206279, upload-time = "2026-10-06T17:32:56.103Z" }, ] [[package]] @@ -512,7 +530,7 @@ wheels = [ [[package]] name = "posthog" -version = "7.75.4" +version = "7.75.11" source = { directory = "../../" } dependencies = [ { name = "backoff" }, @@ -877,12 +895,3 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/46/78/10ad9781128ed2f99dbc474f43283b13fea8ba58723e98844367531c18e9/wrapt-1.17.3-cp314-cp314t-win_arm64.whl", hash = "sha256:f38e60678850c42461d4202739f9bf1e3a737c7ad283638251e79cc49effb6b6", size = 38471, upload-time = "2025-08-12T05:52:57.784Z" }, { url = "https://files.pythonhosted.org/packages/1f/f6/a933bd70f98e9cf3e08167fc5cd7aaaca49147e48411c0bd5ae701bb2194/wrapt-1.17.3-py3-none-any.whl", hash = "sha256:7171ae35d2c33d326ac19dd8facb1e82e5fd04ef8c6c0e394d7af55a55051c22", size = 23591, upload-time = "2025-08-12T05:53:20.674Z" }, ] - -[[package]] -name = "zipp" -version = "3.23.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166", size = 25547, upload-time = "2025-06-08T17:06:39.4Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276, upload-time = "2025-06-08T17:06:38.034Z" }, -] diff --git a/examples/example-ai-cloudflare-ai-gateway/uv.lock b/examples/example-ai-cloudflare-ai-gateway/uv.lock index fad13fdb..b5776059 100644 --- a/examples/example-ai-cloudflare-ai-gateway/uv.lock +++ b/examples/example-ai-cloudflare-ai-gateway/uv.lock @@ -262,18 +262,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/57/b0/0e52c878c53f245edd3a11020f20979b3f490f245af532c7cae3027754b5/idna-3.19-py3-none-any.whl", hash = "sha256:815e7be7a7806d54abb586dc943addc79e8b2ee16915059658cbeff4b1b43bf4", size = 68550, upload-time = "2026-08-18T05:14:22.343Z" }, ] -[[package]] -name = "importlib-metadata" -version = "8.7.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "zipp" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/f3/49/3b30cad09e7771a4982d9975a8cbf64f00d4a1ececb53297f1d9a7be1b10/importlib_metadata-8.7.1.tar.gz", hash = "sha256:49fef1ae6440c182052f407c8d34a68f72efc36db9ca90dc0113398f2fdde8bb", size = 57107, upload-time = "2025-12-21T10:00:19.278Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fa/5e/f8e9a1d23b9c20a551a8a02ea3637b4642e22c2626e3a13a9a29cdea99eb/importlib_metadata-8.7.1-py3-none-any.whl", hash = "sha256:5a1f80bf1daa489495071efbb095d75a634cf28a8bc299581244063b53176151", size = 27865, upload-time = "2025-12-21T10:00:18.329Z" }, -] - [[package]] name = "jiter" version = "0.13.0" @@ -392,50 +380,80 @@ wheels = [ [[package]] name = "opentelemetry-api" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "importlib-metadata" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2c/1d/4049a9e8698361cc1a1aa03a6c59e4fa4c71e0c0f94a30f988a6876a2ae6/opentelemetry_api-1.40.0.tar.gz", hash = "sha256:159be641c0b04d11e9ecd576906462773eb97ae1b657730f0ecf64d32071569f", size = 70851, upload-time = "2026-03-04T14:17:21.555Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2e/02/6e0ae9cc61bd3169d401077b507b3ebc344745171e1051ab430be012dcd9/opentelemetry_api-1.45.1.tar.gz", hash = "sha256:aa38ed19bcc084ba42782a73255b3582283eced7ad6dddbd6695189e69adfb75", size = 72804, upload-time = "2026-10-06T17:32:58.133Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/41/f7dcf80b81ee8e71c1a2b59f14208bc723edbd89ed027a73b175abf6348e/opentelemetry_api-1.45.1-py3-none-any.whl", hash = "sha256:b31553efa588ae44bc306f863c785c5333a9ecc091248c6ee68b4b6c87fdedfb", size = 60256, upload-time = "2026-10-06T17:32:33.506Z" }, +] + +[[package]] +name = "opentelemetry-exporter-http-transport" +version = "0.66b1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/62/0c/e3ebdb4b507f66afcc905e6885a4946969bd75b45988492643356fbbdc63/opentelemetry_exporter_http_transport-0.66b1.tar.gz", hash = "sha256:443080203bf52586ce0b2ad901e8951c61833eab1aa539ae6f1f16fe9e8e7952", size = 11693, upload-time = "2026-10-06T17:32:59.65Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/69/6af86ff66492b481c6a4c05dcfd68beb47ed8ba046440a26a2aac76b95c7/opentelemetry_exporter_http_transport-0.66b1-py3-none-any.whl", hash = "sha256:2f95404bdee7f9d2d529c7de56c7bd86d014d774d8fbf137810e0167f8a492bf", size = 12155, upload-time = "2026-10-06T17:32:35.454Z" }, +] + +[package.optional-dependencies] +requests = [ + { name = "requests" }, +] + +[[package]] +name = "opentelemetry-exporter-otlp-common" +version = "0.66b1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-sdk" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cb/19/41de712173f43057e4532d42ece7d0c6d4210d353e5752433cb14987643f/opentelemetry_exporter_otlp_common-0.66b1.tar.gz", hash = "sha256:6b1403487a2185ac1feb45fd5546fdf8630ce71c36bcefaadf51e2130e9e23f9", size = 14325, upload-time = "2026-10-06T17:33:01.725Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5f/bf/93795954016c522008da367da292adceed71cca6ee1717e1d64c83089099/opentelemetry_api-1.40.0-py3-none-any.whl", hash = "sha256:82dd69331ae74b06f6a874704be0cfaa49a1650e1537d4a813b86ecef7d0ecf9", size = 68676, upload-time = "2026-03-04T14:17:01.24Z" }, + { url = "https://files.pythonhosted.org/packages/fc/39/8c23d67665c762aa51840fa06f86e902e8f6f1693bc8d7e3d98cd6e2f753/opentelemetry_exporter_otlp_common-0.66b1-py3-none-any.whl", hash = "sha256:00ff8592c3a7cb729ff3fdc7ffa12372c243bdf2163e80c180994d0c7bd83ee9", size = 12385, upload-time = "2026-10-06T17:32:38.177Z" }, ] [[package]] name = "opentelemetry-exporter-otlp-proto-common" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-proto" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/51/bc/1559d46557fe6eca0b46c88d4c2676285f1f3be2e8d06bb5d15fbffc814a/opentelemetry_exporter_otlp_proto_common-1.40.0.tar.gz", hash = "sha256:1cbee86a4064790b362a86601ee7934f368b81cd4cc2f2e163902a6e7818a0fa", size = 20416, upload-time = "2026-03-04T14:17:23.801Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c1/8e/65e85e5137991a3c493b11682151d198638a5bc1dd4b4c5f67e013c57d7c/opentelemetry_exporter_otlp_proto_common-1.45.1.tar.gz", hash = "sha256:2e4adcc3a67bcf57804fc49514f0ef64974ca7590aa3491da389852b4a0628f6", size = 18873, upload-time = "2026-10-06T17:33:04.471Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8b/ca/8f122055c97a932311a3f640273f084e738008933503d0c2563cd5d591fc/opentelemetry_exporter_otlp_proto_common-1.40.0-py3-none-any.whl", hash = "sha256:7081ff453835a82417bf38dccf122c827c3cbc94f2079b03bba02a3165f25149", size = 18369, upload-time = "2026-03-04T14:17:04.796Z" }, + { url = "https://files.pythonhosted.org/packages/84/aa/92f225d353904e7f70b8b3e3c1b02db0cf56f744c2e83c581dc372e78873/opentelemetry_exporter_otlp_proto_common-1.45.1-py3-none-any.whl", hash = "sha256:2f446183ae7047b036226f1d846c41a834b0e8755ad13b51a51dd38952eb466c", size = 15393, upload-time = "2026-10-06T17:32:41.911Z" }, ] [[package]] name = "opentelemetry-exporter-otlp-proto-http" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "googleapis-common-protos" }, { name = "opentelemetry-api" }, + { name = "opentelemetry-exporter-http-transport", extra = ["requests"] }, + { name = "opentelemetry-exporter-otlp-common" }, { name = "opentelemetry-exporter-otlp-proto-common" }, { name = "opentelemetry-proto" }, { name = "opentelemetry-sdk" }, { name = "requests" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2e/fa/73d50e2c15c56be4d000c98e24221d494674b0cc95524e2a8cb3856d95a4/opentelemetry_exporter_otlp_proto_http-1.40.0.tar.gz", hash = "sha256:db48f5e0f33217588bbc00274a31517ba830da576e59503507c839b38fa0869c", size = 17772, upload-time = "2026-03-04T14:17:25.324Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/17/26487707ea4caa97b17e6e4b5fa72133a53512ffa2f5cf7a49ef284b29cb/opentelemetry_exporter_otlp_proto_http-1.45.1.tar.gz", hash = "sha256:45c218405ce3fd879596924b1874bf9a8f6880206d61065c5a912c8e5c297fb7", size = 28839, upload-time = "2026-10-06T17:33:05.713Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/3a/8865d6754e61c9fb170cdd530a124a53769ee5f740236064816eb0ca7301/opentelemetry_exporter_otlp_proto_http-1.40.0-py3-none-any.whl", hash = "sha256:a8d1dab28f504c5d96577d6509f80a8150e44e8f45f82cdbe0e34c99ab040069", size = 19960, upload-time = "2026-03-04T14:17:07.153Z" }, + { url = "https://files.pythonhosted.org/packages/aa/1f/517eaa0187ba106a9da97160ce2add3a371812681dc440930b267f714e42/opentelemetry_exporter_otlp_proto_http-1.45.1-py3-none-any.whl", hash = "sha256:24a97cf3753c7fb52fad44a696e452ff371686339e2acf3309e2eda3d0230700", size = 22180, upload-time = "2026-10-06T17:32:43.946Z" }, ] [[package]] name = "opentelemetry-instrumentation" -version = "0.61b0" +version = "0.66b1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, @@ -443,9 +461,9 @@ dependencies = [ { name = "packaging" }, { name = "wrapt" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/da/37/6bf8e66bfcee5d3c6515b79cb2ee9ad05fe573c20f7ceb288d0e7eeec28c/opentelemetry_instrumentation-0.61b0.tar.gz", hash = "sha256:cb21b48db738c9de196eba6b805b4ff9de3b7f187e4bbf9a466fa170514f1fc7", size = 32606, upload-time = "2026-03-04T14:20:16.825Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a5/03/89e47ff8d52a4f83b343e6eb9ef1698ff45357216e5b6b2b21e0da5c5c7d/opentelemetry_instrumentation-0.66b1.tar.gz", hash = "sha256:e79a510f7d87c72d95e964ddb42193a0d9a75668c027d980eab032ea1322a5ce", size = 43308, upload-time = "2026-10-06T17:36:10.703Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d8/3e/f6f10f178b6316de67f0dfdbbb699a24fbe8917cf1743c1595fb9dcdd461/opentelemetry_instrumentation-0.61b0-py3-none-any.whl", hash = "sha256:92a93a280e69788e8f88391247cc530fd81f16f2b011979d4d6398f805cfbc63", size = 33448, upload-time = "2026-03-04T14:19:02.447Z" }, + { url = "https://files.pythonhosted.org/packages/da/b2/d1413681ff43e13ac9860df27e1226d3199ab0b97b352ceea41abcc660a5/opentelemetry_instrumentation-0.66b1-py3-none-any.whl", hash = "sha256:4c4aa14dc9a24a02325a9d4c42c4d0208dbb1374c2b1b8fe6c9392d59f3e1008", size = 36904, upload-time = "2026-10-06T17:35:11.663Z" }, ] [[package]] @@ -464,41 +482,41 @@ wheels = [ [[package]] name = "opentelemetry-proto" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "protobuf" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4c/77/dd38991db037fdfce45849491cb61de5ab000f49824a00230afb112a4392/opentelemetry_proto-1.40.0.tar.gz", hash = "sha256:03f639ca129ba513f5819810f5b1f42bcb371391405d99c168fe6937c62febcd", size = 45667, upload-time = "2026-03-04T14:17:31.194Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4b/7f/15f014fb195da6c2dbb6c71399b8e76824878718e94de6454038488eed28/opentelemetry_proto-1.45.1.tar.gz", hash = "sha256:79e0fb95e4616691a469439238aa9224d75779b3e108e895d1aa125ab29ca77c", size = 46488, upload-time = "2026-10-06T17:33:11.49Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b9/b2/189b2577dde745b15625b3214302605b1353436219d42b7912e77fa8dc24/opentelemetry_proto-1.40.0-py3-none-any.whl", hash = "sha256:266c4385d88923a23d63e353e9761af0f47a6ed0d486979777fe4de59dc9b25f", size = 72073, upload-time = "2026-03-04T14:17:16.673Z" }, + { url = "https://files.pythonhosted.org/packages/ab/9a/42ec8180a769516ae757e893b69736826efceac7332553915b4528a91c6d/opentelemetry_proto-1.45.1-py3-none-any.whl", hash = "sha256:f38e2a8413053c180cd3d2637fbb279673ec2f6a6e09c995aafa2f452c52b46e", size = 72488, upload-time = "2026-10-06T17:32:53.057Z" }, ] [[package]] name = "opentelemetry-sdk" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, { name = "opentelemetry-semantic-conventions" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/58/fd/3c3125b20ba18ce2155ba9ea74acb0ae5d25f8cd39cfd37455601b7955cc/opentelemetry_sdk-1.40.0.tar.gz", hash = "sha256:18e9f5ec20d859d268c7cb3c5198c8d105d073714db3de50b593b8c1345a48f2", size = 184252, upload-time = "2026-03-04T14:17:31.87Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/79/7392e21a1c8f0c61d90b223e31c7e48cb9d452e91a6b820ad24cca5f23c4/opentelemetry_sdk-1.45.1.tar.gz", hash = "sha256:63d24a6ca645019a631e6a51999c73e93adcac1196ca640b8ae78a7cc4762bf3", size = 218324, upload-time = "2026-10-06T17:33:13.26Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/c5/6a852903d8bfac758c6dc6e9a68b015d3c33f2f1be5e9591e0f4b69c7e0a/opentelemetry_sdk-1.40.0-py3-none-any.whl", hash = "sha256:787d2154a71f4b3d81f20524a8ce061b7db667d24e46753f32a7bc48f1c1f3f1", size = 141951, upload-time = "2026-03-04T14:17:17.961Z" }, + { url = "https://files.pythonhosted.org/packages/95/3c/87c42b4bd6dd297536f04cd9383d212ac557ecd49f2cbdcd46da1c9ef5c8/opentelemetry_sdk-1.45.1-py3-none-any.whl", hash = "sha256:c604c11dc429810812348989115fa44bd558772a3d7442afc43d024f2c250ca4", size = 140063, upload-time = "2026-10-06T17:32:55.04Z" }, ] [[package]] name = "opentelemetry-semantic-conventions" -version = "0.61b0" +version = "0.66b1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6d/c0/4ae7973f3c2cfd2b6e321f1675626f0dab0a97027cc7a297474c9c8f3d04/opentelemetry_semantic_conventions-0.61b0.tar.gz", hash = "sha256:072f65473c5d7c6dc0355b27d6c9d1a679d63b6d4b4b16a9773062cb7e31192a", size = 145755, upload-time = "2026-03-04T14:17:32.664Z" } +sdist = { url = "https://files.pythonhosted.org/packages/46/e4/dbbfb2a010c4db2224a5114638acede6fe563d33cc20fb1752cebcbe6298/opentelemetry_semantic_conventions-0.66b1.tar.gz", hash = "sha256:497ca63bf383723411e8eaf60c8779e9877633c936bb641080adab59d0eb6ec8", size = 150250, upload-time = "2026-10-06T17:33:14.073Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b2/37/cc6a55e448deaa9b27377d087da8615a3416d8ad523d5960b78dbeadd02a/opentelemetry_semantic_conventions-0.61b0-py3-none-any.whl", hash = "sha256:fa530a96be229795f8cef353739b618148b0fe2b4b3f005e60e262926c4d38e2", size = 231621, upload-time = "2026-03-04T14:17:19.33Z" }, + { url = "https://files.pythonhosted.org/packages/bc/14/67f8aa798857f8cf686f515bf93d9bb877ce952ddc8efae0fa25b45ce0d6/opentelemetry_semantic_conventions-0.66b1-py3-none-any.whl", hash = "sha256:d4cddeb4315490b35213f55e2bdc9ac54bb1e4d318927475bed62b35545e581b", size = 206279, upload-time = "2026-10-06T17:32:56.103Z" }, ] [[package]] @@ -512,7 +530,7 @@ wheels = [ [[package]] name = "posthog" -version = "7.75.4" +version = "7.75.11" source = { directory = "../../" } dependencies = [ { name = "backoff" }, @@ -877,12 +895,3 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/46/78/10ad9781128ed2f99dbc474f43283b13fea8ba58723e98844367531c18e9/wrapt-1.17.3-cp314-cp314t-win_arm64.whl", hash = "sha256:f38e60678850c42461d4202739f9bf1e3a737c7ad283638251e79cc49effb6b6", size = 38471, upload-time = "2025-08-12T05:52:57.784Z" }, { url = "https://files.pythonhosted.org/packages/1f/f6/a933bd70f98e9cf3e08167fc5cd7aaaca49147e48411c0bd5ae701bb2194/wrapt-1.17.3-py3-none-any.whl", hash = "sha256:7171ae35d2c33d326ac19dd8facb1e82e5fd04ef8c6c0e394d7af55a55051c22", size = 23591, upload-time = "2025-08-12T05:53:20.674Z" }, ] - -[[package]] -name = "zipp" -version = "3.23.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166", size = 25547, upload-time = "2025-06-08T17:06:39.4Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276, upload-time = "2025-06-08T17:06:38.034Z" }, -] diff --git a/examples/example-ai-cohere/uv.lock b/examples/example-ai-cohere/uv.lock index 0e7c2a2a..19e58c7b 100644 --- a/examples/example-ai-cohere/uv.lock +++ b/examples/example-ai-cohere/uv.lock @@ -262,18 +262,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/57/b0/0e52c878c53f245edd3a11020f20979b3f490f245af532c7cae3027754b5/idna-3.19-py3-none-any.whl", hash = "sha256:815e7be7a7806d54abb586dc943addc79e8b2ee16915059658cbeff4b1b43bf4", size = 68550, upload-time = "2026-08-18T05:14:22.343Z" }, ] -[[package]] -name = "importlib-metadata" -version = "8.7.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "zipp" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/f3/49/3b30cad09e7771a4982d9975a8cbf64f00d4a1ececb53297f1d9a7be1b10/importlib_metadata-8.7.1.tar.gz", hash = "sha256:49fef1ae6440c182052f407c8d34a68f72efc36db9ca90dc0113398f2fdde8bb", size = 57107, upload-time = "2025-12-21T10:00:19.278Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fa/5e/f8e9a1d23b9c20a551a8a02ea3637b4642e22c2626e3a13a9a29cdea99eb/importlib_metadata-8.7.1-py3-none-any.whl", hash = "sha256:5a1f80bf1daa489495071efbb095d75a634cf28a8bc299581244063b53176151", size = 27865, upload-time = "2025-12-21T10:00:18.329Z" }, -] - [[package]] name = "jiter" version = "0.13.0" @@ -392,50 +380,80 @@ wheels = [ [[package]] name = "opentelemetry-api" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "importlib-metadata" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2c/1d/4049a9e8698361cc1a1aa03a6c59e4fa4c71e0c0f94a30f988a6876a2ae6/opentelemetry_api-1.40.0.tar.gz", hash = "sha256:159be641c0b04d11e9ecd576906462773eb97ae1b657730f0ecf64d32071569f", size = 70851, upload-time = "2026-03-04T14:17:21.555Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2e/02/6e0ae9cc61bd3169d401077b507b3ebc344745171e1051ab430be012dcd9/opentelemetry_api-1.45.1.tar.gz", hash = "sha256:aa38ed19bcc084ba42782a73255b3582283eced7ad6dddbd6695189e69adfb75", size = 72804, upload-time = "2026-10-06T17:32:58.133Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/41/f7dcf80b81ee8e71c1a2b59f14208bc723edbd89ed027a73b175abf6348e/opentelemetry_api-1.45.1-py3-none-any.whl", hash = "sha256:b31553efa588ae44bc306f863c785c5333a9ecc091248c6ee68b4b6c87fdedfb", size = 60256, upload-time = "2026-10-06T17:32:33.506Z" }, +] + +[[package]] +name = "opentelemetry-exporter-http-transport" +version = "0.66b1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/62/0c/e3ebdb4b507f66afcc905e6885a4946969bd75b45988492643356fbbdc63/opentelemetry_exporter_http_transport-0.66b1.tar.gz", hash = "sha256:443080203bf52586ce0b2ad901e8951c61833eab1aa539ae6f1f16fe9e8e7952", size = 11693, upload-time = "2026-10-06T17:32:59.65Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/69/6af86ff66492b481c6a4c05dcfd68beb47ed8ba046440a26a2aac76b95c7/opentelemetry_exporter_http_transport-0.66b1-py3-none-any.whl", hash = "sha256:2f95404bdee7f9d2d529c7de56c7bd86d014d774d8fbf137810e0167f8a492bf", size = 12155, upload-time = "2026-10-06T17:32:35.454Z" }, +] + +[package.optional-dependencies] +requests = [ + { name = "requests" }, +] + +[[package]] +name = "opentelemetry-exporter-otlp-common" +version = "0.66b1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-sdk" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cb/19/41de712173f43057e4532d42ece7d0c6d4210d353e5752433cb14987643f/opentelemetry_exporter_otlp_common-0.66b1.tar.gz", hash = "sha256:6b1403487a2185ac1feb45fd5546fdf8630ce71c36bcefaadf51e2130e9e23f9", size = 14325, upload-time = "2026-10-06T17:33:01.725Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5f/bf/93795954016c522008da367da292adceed71cca6ee1717e1d64c83089099/opentelemetry_api-1.40.0-py3-none-any.whl", hash = "sha256:82dd69331ae74b06f6a874704be0cfaa49a1650e1537d4a813b86ecef7d0ecf9", size = 68676, upload-time = "2026-03-04T14:17:01.24Z" }, + { url = "https://files.pythonhosted.org/packages/fc/39/8c23d67665c762aa51840fa06f86e902e8f6f1693bc8d7e3d98cd6e2f753/opentelemetry_exporter_otlp_common-0.66b1-py3-none-any.whl", hash = "sha256:00ff8592c3a7cb729ff3fdc7ffa12372c243bdf2163e80c180994d0c7bd83ee9", size = 12385, upload-time = "2026-10-06T17:32:38.177Z" }, ] [[package]] name = "opentelemetry-exporter-otlp-proto-common" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-proto" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/51/bc/1559d46557fe6eca0b46c88d4c2676285f1f3be2e8d06bb5d15fbffc814a/opentelemetry_exporter_otlp_proto_common-1.40.0.tar.gz", hash = "sha256:1cbee86a4064790b362a86601ee7934f368b81cd4cc2f2e163902a6e7818a0fa", size = 20416, upload-time = "2026-03-04T14:17:23.801Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c1/8e/65e85e5137991a3c493b11682151d198638a5bc1dd4b4c5f67e013c57d7c/opentelemetry_exporter_otlp_proto_common-1.45.1.tar.gz", hash = "sha256:2e4adcc3a67bcf57804fc49514f0ef64974ca7590aa3491da389852b4a0628f6", size = 18873, upload-time = "2026-10-06T17:33:04.471Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8b/ca/8f122055c97a932311a3f640273f084e738008933503d0c2563cd5d591fc/opentelemetry_exporter_otlp_proto_common-1.40.0-py3-none-any.whl", hash = "sha256:7081ff453835a82417bf38dccf122c827c3cbc94f2079b03bba02a3165f25149", size = 18369, upload-time = "2026-03-04T14:17:04.796Z" }, + { url = "https://files.pythonhosted.org/packages/84/aa/92f225d353904e7f70b8b3e3c1b02db0cf56f744c2e83c581dc372e78873/opentelemetry_exporter_otlp_proto_common-1.45.1-py3-none-any.whl", hash = "sha256:2f446183ae7047b036226f1d846c41a834b0e8755ad13b51a51dd38952eb466c", size = 15393, upload-time = "2026-10-06T17:32:41.911Z" }, ] [[package]] name = "opentelemetry-exporter-otlp-proto-http" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "googleapis-common-protos" }, { name = "opentelemetry-api" }, + { name = "opentelemetry-exporter-http-transport", extra = ["requests"] }, + { name = "opentelemetry-exporter-otlp-common" }, { name = "opentelemetry-exporter-otlp-proto-common" }, { name = "opentelemetry-proto" }, { name = "opentelemetry-sdk" }, { name = "requests" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2e/fa/73d50e2c15c56be4d000c98e24221d494674b0cc95524e2a8cb3856d95a4/opentelemetry_exporter_otlp_proto_http-1.40.0.tar.gz", hash = "sha256:db48f5e0f33217588bbc00274a31517ba830da576e59503507c839b38fa0869c", size = 17772, upload-time = "2026-03-04T14:17:25.324Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/17/26487707ea4caa97b17e6e4b5fa72133a53512ffa2f5cf7a49ef284b29cb/opentelemetry_exporter_otlp_proto_http-1.45.1.tar.gz", hash = "sha256:45c218405ce3fd879596924b1874bf9a8f6880206d61065c5a912c8e5c297fb7", size = 28839, upload-time = "2026-10-06T17:33:05.713Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/3a/8865d6754e61c9fb170cdd530a124a53769ee5f740236064816eb0ca7301/opentelemetry_exporter_otlp_proto_http-1.40.0-py3-none-any.whl", hash = "sha256:a8d1dab28f504c5d96577d6509f80a8150e44e8f45f82cdbe0e34c99ab040069", size = 19960, upload-time = "2026-03-04T14:17:07.153Z" }, + { url = "https://files.pythonhosted.org/packages/aa/1f/517eaa0187ba106a9da97160ce2add3a371812681dc440930b267f714e42/opentelemetry_exporter_otlp_proto_http-1.45.1-py3-none-any.whl", hash = "sha256:24a97cf3753c7fb52fad44a696e452ff371686339e2acf3309e2eda3d0230700", size = 22180, upload-time = "2026-10-06T17:32:43.946Z" }, ] [[package]] name = "opentelemetry-instrumentation" -version = "0.61b0" +version = "0.66b1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, @@ -443,9 +461,9 @@ dependencies = [ { name = "packaging" }, { name = "wrapt" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/da/37/6bf8e66bfcee5d3c6515b79cb2ee9ad05fe573c20f7ceb288d0e7eeec28c/opentelemetry_instrumentation-0.61b0.tar.gz", hash = "sha256:cb21b48db738c9de196eba6b805b4ff9de3b7f187e4bbf9a466fa170514f1fc7", size = 32606, upload-time = "2026-03-04T14:20:16.825Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a5/03/89e47ff8d52a4f83b343e6eb9ef1698ff45357216e5b6b2b21e0da5c5c7d/opentelemetry_instrumentation-0.66b1.tar.gz", hash = "sha256:e79a510f7d87c72d95e964ddb42193a0d9a75668c027d980eab032ea1322a5ce", size = 43308, upload-time = "2026-10-06T17:36:10.703Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d8/3e/f6f10f178b6316de67f0dfdbbb699a24fbe8917cf1743c1595fb9dcdd461/opentelemetry_instrumentation-0.61b0-py3-none-any.whl", hash = "sha256:92a93a280e69788e8f88391247cc530fd81f16f2b011979d4d6398f805cfbc63", size = 33448, upload-time = "2026-03-04T14:19:02.447Z" }, + { url = "https://files.pythonhosted.org/packages/da/b2/d1413681ff43e13ac9860df27e1226d3199ab0b97b352ceea41abcc660a5/opentelemetry_instrumentation-0.66b1-py3-none-any.whl", hash = "sha256:4c4aa14dc9a24a02325a9d4c42c4d0208dbb1374c2b1b8fe6c9392d59f3e1008", size = 36904, upload-time = "2026-10-06T17:35:11.663Z" }, ] [[package]] @@ -464,41 +482,41 @@ wheels = [ [[package]] name = "opentelemetry-proto" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "protobuf" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4c/77/dd38991db037fdfce45849491cb61de5ab000f49824a00230afb112a4392/opentelemetry_proto-1.40.0.tar.gz", hash = "sha256:03f639ca129ba513f5819810f5b1f42bcb371391405d99c168fe6937c62febcd", size = 45667, upload-time = "2026-03-04T14:17:31.194Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4b/7f/15f014fb195da6c2dbb6c71399b8e76824878718e94de6454038488eed28/opentelemetry_proto-1.45.1.tar.gz", hash = "sha256:79e0fb95e4616691a469439238aa9224d75779b3e108e895d1aa125ab29ca77c", size = 46488, upload-time = "2026-10-06T17:33:11.49Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b9/b2/189b2577dde745b15625b3214302605b1353436219d42b7912e77fa8dc24/opentelemetry_proto-1.40.0-py3-none-any.whl", hash = "sha256:266c4385d88923a23d63e353e9761af0f47a6ed0d486979777fe4de59dc9b25f", size = 72073, upload-time = "2026-03-04T14:17:16.673Z" }, + { url = "https://files.pythonhosted.org/packages/ab/9a/42ec8180a769516ae757e893b69736826efceac7332553915b4528a91c6d/opentelemetry_proto-1.45.1-py3-none-any.whl", hash = "sha256:f38e2a8413053c180cd3d2637fbb279673ec2f6a6e09c995aafa2f452c52b46e", size = 72488, upload-time = "2026-10-06T17:32:53.057Z" }, ] [[package]] name = "opentelemetry-sdk" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, { name = "opentelemetry-semantic-conventions" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/58/fd/3c3125b20ba18ce2155ba9ea74acb0ae5d25f8cd39cfd37455601b7955cc/opentelemetry_sdk-1.40.0.tar.gz", hash = "sha256:18e9f5ec20d859d268c7cb3c5198c8d105d073714db3de50b593b8c1345a48f2", size = 184252, upload-time = "2026-03-04T14:17:31.87Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/79/7392e21a1c8f0c61d90b223e31c7e48cb9d452e91a6b820ad24cca5f23c4/opentelemetry_sdk-1.45.1.tar.gz", hash = "sha256:63d24a6ca645019a631e6a51999c73e93adcac1196ca640b8ae78a7cc4762bf3", size = 218324, upload-time = "2026-10-06T17:33:13.26Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/c5/6a852903d8bfac758c6dc6e9a68b015d3c33f2f1be5e9591e0f4b69c7e0a/opentelemetry_sdk-1.40.0-py3-none-any.whl", hash = "sha256:787d2154a71f4b3d81f20524a8ce061b7db667d24e46753f32a7bc48f1c1f3f1", size = 141951, upload-time = "2026-03-04T14:17:17.961Z" }, + { url = "https://files.pythonhosted.org/packages/95/3c/87c42b4bd6dd297536f04cd9383d212ac557ecd49f2cbdcd46da1c9ef5c8/opentelemetry_sdk-1.45.1-py3-none-any.whl", hash = "sha256:c604c11dc429810812348989115fa44bd558772a3d7442afc43d024f2c250ca4", size = 140063, upload-time = "2026-10-06T17:32:55.04Z" }, ] [[package]] name = "opentelemetry-semantic-conventions" -version = "0.61b0" +version = "0.66b1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6d/c0/4ae7973f3c2cfd2b6e321f1675626f0dab0a97027cc7a297474c9c8f3d04/opentelemetry_semantic_conventions-0.61b0.tar.gz", hash = "sha256:072f65473c5d7c6dc0355b27d6c9d1a679d63b6d4b4b16a9773062cb7e31192a", size = 145755, upload-time = "2026-03-04T14:17:32.664Z" } +sdist = { url = "https://files.pythonhosted.org/packages/46/e4/dbbfb2a010c4db2224a5114638acede6fe563d33cc20fb1752cebcbe6298/opentelemetry_semantic_conventions-0.66b1.tar.gz", hash = "sha256:497ca63bf383723411e8eaf60c8779e9877633c936bb641080adab59d0eb6ec8", size = 150250, upload-time = "2026-10-06T17:33:14.073Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b2/37/cc6a55e448deaa9b27377d087da8615a3416d8ad523d5960b78dbeadd02a/opentelemetry_semantic_conventions-0.61b0-py3-none-any.whl", hash = "sha256:fa530a96be229795f8cef353739b618148b0fe2b4b3f005e60e262926c4d38e2", size = 231621, upload-time = "2026-03-04T14:17:19.33Z" }, + { url = "https://files.pythonhosted.org/packages/bc/14/67f8aa798857f8cf686f515bf93d9bb877ce952ddc8efae0fa25b45ce0d6/opentelemetry_semantic_conventions-0.66b1-py3-none-any.whl", hash = "sha256:d4cddeb4315490b35213f55e2bdc9ac54bb1e4d318927475bed62b35545e581b", size = 206279, upload-time = "2026-10-06T17:32:56.103Z" }, ] [[package]] @@ -512,7 +530,7 @@ wheels = [ [[package]] name = "posthog" -version = "7.75.4" +version = "7.75.11" source = { directory = "../../" } dependencies = [ { name = "backoff" }, @@ -877,12 +895,3 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/46/78/10ad9781128ed2f99dbc474f43283b13fea8ba58723e98844367531c18e9/wrapt-1.17.3-cp314-cp314t-win_arm64.whl", hash = "sha256:f38e60678850c42461d4202739f9bf1e3a737c7ad283638251e79cc49effb6b6", size = 38471, upload-time = "2025-08-12T05:52:57.784Z" }, { url = "https://files.pythonhosted.org/packages/1f/f6/a933bd70f98e9cf3e08167fc5cd7aaaca49147e48411c0bd5ae701bb2194/wrapt-1.17.3-py3-none-any.whl", hash = "sha256:7171ae35d2c33d326ac19dd8facb1e82e5fd04ef8c6c0e394d7af55a55051c22", size = 23591, upload-time = "2025-08-12T05:53:20.674Z" }, ] - -[[package]] -name = "zipp" -version = "3.23.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166", size = 25547, upload-time = "2025-06-08T17:06:39.4Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276, upload-time = "2025-06-08T17:06:38.034Z" }, -] diff --git a/examples/example-ai-crewai/uv.lock b/examples/example-ai-crewai/uv.lock index 8589391e..2d731ef0 100644 --- a/examples/example-ai-crewai/uv.lock +++ b/examples/example-ai-crewai/uv.lock @@ -2316,102 +2316,133 @@ wheels = [ [[package]] name = "opentelemetry-api" -version = "1.34.1" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "importlib-metadata" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4d/5e/94a8cb759e4e409022229418294e098ca7feca00eb3c467bb20cbd329bda/opentelemetry_api-1.34.1.tar.gz", hash = "sha256:64f0bd06d42824843731d05beea88d4d4b6ae59f9fe347ff7dfa2cc14233bbb3", size = 64987, upload-time = "2025-06-10T08:55:19.818Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2e/02/6e0ae9cc61bd3169d401077b507b3ebc344745171e1051ab430be012dcd9/opentelemetry_api-1.45.1.tar.gz", hash = "sha256:aa38ed19bcc084ba42782a73255b3582283eced7ad6dddbd6695189e69adfb75", size = 72804, upload-time = "2026-10-06T17:32:58.133Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/41/f7dcf80b81ee8e71c1a2b59f14208bc723edbd89ed027a73b175abf6348e/opentelemetry_api-1.45.1-py3-none-any.whl", hash = "sha256:b31553efa588ae44bc306f863c785c5333a9ecc091248c6ee68b4b6c87fdedfb", size = 60256, upload-time = "2026-10-06T17:32:33.506Z" }, +] + +[[package]] +name = "opentelemetry-exporter-http-transport" +version = "0.66b1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/62/0c/e3ebdb4b507f66afcc905e6885a4946969bd75b45988492643356fbbdc63/opentelemetry_exporter_http_transport-0.66b1.tar.gz", hash = "sha256:443080203bf52586ce0b2ad901e8951c61833eab1aa539ae6f1f16fe9e8e7952", size = 11693, upload-time = "2026-10-06T17:32:59.65Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/69/6af86ff66492b481c6a4c05dcfd68beb47ed8ba046440a26a2aac76b95c7/opentelemetry_exporter_http_transport-0.66b1-py3-none-any.whl", hash = "sha256:2f95404bdee7f9d2d529c7de56c7bd86d014d774d8fbf137810e0167f8a492bf", size = 12155, upload-time = "2026-10-06T17:32:35.454Z" }, +] + +[package.optional-dependencies] +requests = [ + { name = "requests" }, +] + +[[package]] +name = "opentelemetry-exporter-otlp-common" +version = "0.66b1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-sdk" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cb/19/41de712173f43057e4532d42ece7d0c6d4210d353e5752433cb14987643f/opentelemetry_exporter_otlp_common-0.66b1.tar.gz", hash = "sha256:6b1403487a2185ac1feb45fd5546fdf8630ce71c36bcefaadf51e2130e9e23f9", size = 14325, upload-time = "2026-10-06T17:33:01.725Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a5/3a/2ba85557e8dc024c0842ad22c570418dc02c36cbd1ab4b832a93edf071b8/opentelemetry_api-1.34.1-py3-none-any.whl", hash = "sha256:b7df4cb0830d5a6c29ad0c0691dbae874d8daefa934b8b1d642de48323d32a8c", size = 65767, upload-time = "2025-06-10T08:54:56.717Z" }, + { url = "https://files.pythonhosted.org/packages/fc/39/8c23d67665c762aa51840fa06f86e902e8f6f1693bc8d7e3d98cd6e2f753/opentelemetry_exporter_otlp_common-0.66b1-py3-none-any.whl", hash = "sha256:00ff8592c3a7cb729ff3fdc7ffa12372c243bdf2163e80c180994d0c7bd83ee9", size = 12385, upload-time = "2026-10-06T17:32:38.177Z" }, ] [[package]] name = "opentelemetry-exporter-otlp-proto-common" -version = "1.34.1" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-proto" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/86/f0/ff235936ee40db93360233b62da932d4fd9e8d103cd090c6bcb9afaf5f01/opentelemetry_exporter_otlp_proto_common-1.34.1.tar.gz", hash = "sha256:b59a20a927facd5eac06edaf87a07e49f9e4a13db487b7d8a52b37cb87710f8b", size = 20817, upload-time = "2025-06-10T08:55:22.55Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c1/8e/65e85e5137991a3c493b11682151d198638a5bc1dd4b4c5f67e013c57d7c/opentelemetry_exporter_otlp_proto_common-1.45.1.tar.gz", hash = "sha256:2e4adcc3a67bcf57804fc49514f0ef64974ca7590aa3491da389852b4a0628f6", size = 18873, upload-time = "2026-10-06T17:33:04.471Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/72/e8/8b292a11cc8d8d87ec0c4089ae21b6a58af49ca2e51fa916435bc922fdc7/opentelemetry_exporter_otlp_proto_common-1.34.1-py3-none-any.whl", hash = "sha256:8e2019284bf24d3deebbb6c59c71e6eef3307cd88eff8c633e061abba33f7e87", size = 18834, upload-time = "2025-06-10T08:55:00.806Z" }, + { url = "https://files.pythonhosted.org/packages/84/aa/92f225d353904e7f70b8b3e3c1b02db0cf56f744c2e83c581dc372e78873/opentelemetry_exporter_otlp_proto_common-1.45.1-py3-none-any.whl", hash = "sha256:2f446183ae7047b036226f1d846c41a834b0e8755ad13b51a51dd38952eb466c", size = 15393, upload-time = "2026-10-06T17:32:41.911Z" }, ] [[package]] name = "opentelemetry-exporter-otlp-proto-grpc" -version = "1.34.1" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "googleapis-common-protos" }, { name = "grpcio" }, { name = "opentelemetry-api" }, + { name = "opentelemetry-exporter-otlp-common" }, { name = "opentelemetry-exporter-otlp-proto-common" }, { name = "opentelemetry-proto" }, { name = "opentelemetry-sdk" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/41/f7/bb63837a3edb9ca857aaf5760796874e7cecddc88a2571b0992865a48fb6/opentelemetry_exporter_otlp_proto_grpc-1.34.1.tar.gz", hash = "sha256:7c841b90caa3aafcfc4fee58487a6c71743c34c6dc1787089d8b0578bbd794dd", size = 22566, upload-time = "2025-06-10T08:55:23.214Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/00/a82af0be959dc58495740b169c6669a86e0811f6cd353a01eda34d255db3/opentelemetry_exporter_otlp_proto_grpc-1.45.1.tar.gz", hash = "sha256:3b3dcfbfdcb4e35149fcf309972282054b45228f5c10547d0095d6578510a9a0", size = 25997, upload-time = "2026-10-06T17:33:05.114Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b4/42/0a4dd47e7ef54edf670c81fc06a83d68ea42727b82126a1df9dd0477695d/opentelemetry_exporter_otlp_proto_grpc-1.34.1-py3-none-any.whl", hash = "sha256:04bb8b732b02295be79f8a86a4ad28fae3d4ddb07307a98c7aa6f331de18cca6", size = 18615, upload-time = "2025-06-10T08:55:02.214Z" }, + { url = "https://files.pythonhosted.org/packages/4d/46/2d1da202f1e17c81aae7efcf702898d524b46709e4d3e2bf1f7f8ca8fbc6/opentelemetry_exporter_otlp_proto_grpc-1.45.1-py3-none-any.whl", hash = "sha256:e42ecb789d2fc5d8145e3dadc3e2991c9f18cd166d7c7514e234702540274b76", size = 19492, upload-time = "2026-10-06T17:32:42.838Z" }, ] [[package]] name = "opentelemetry-exporter-otlp-proto-http" -version = "1.34.1" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "googleapis-common-protos" }, { name = "opentelemetry-api" }, + { name = "opentelemetry-exporter-http-transport", extra = ["requests"] }, + { name = "opentelemetry-exporter-otlp-common" }, { name = "opentelemetry-exporter-otlp-proto-common" }, { name = "opentelemetry-proto" }, { name = "opentelemetry-sdk" }, { name = "requests" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/19/8f/954bc725961cbe425a749d55c0ba1df46832a5999eae764d1a7349ac1c29/opentelemetry_exporter_otlp_proto_http-1.34.1.tar.gz", hash = "sha256:aaac36fdce46a8191e604dcf632e1f9380c7d5b356b27b3e0edb5610d9be28ad", size = 15351, upload-time = "2025-06-10T08:55:24.657Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/17/26487707ea4caa97b17e6e4b5fa72133a53512ffa2f5cf7a49ef284b29cb/opentelemetry_exporter_otlp_proto_http-1.45.1.tar.gz", hash = "sha256:45c218405ce3fd879596924b1874bf9a8f6880206d61065c5a912c8e5c297fb7", size = 28839, upload-time = "2026-10-06T17:33:05.713Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/79/54/b05251c04e30c1ac70cf4a7c5653c085dfcf2c8b98af71661d6a252adc39/opentelemetry_exporter_otlp_proto_http-1.34.1-py3-none-any.whl", hash = "sha256:5251f00ca85872ce50d871f6d3cc89fe203b94c3c14c964bbdc3883366c705d8", size = 17744, upload-time = "2025-06-10T08:55:03.802Z" }, + { url = "https://files.pythonhosted.org/packages/aa/1f/517eaa0187ba106a9da97160ce2add3a371812681dc440930b267f714e42/opentelemetry_exporter_otlp_proto_http-1.45.1-py3-none-any.whl", hash = "sha256:24a97cf3753c7fb52fad44a696e452ff371686339e2acf3309e2eda3d0230700", size = 22180, upload-time = "2026-10-06T17:32:43.946Z" }, ] [[package]] name = "opentelemetry-proto" -version = "1.34.1" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "protobuf" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/66/b3/c3158dd012463bb7c0eb7304a85a6f63baeeb5b4c93a53845cf89f848c7e/opentelemetry_proto-1.34.1.tar.gz", hash = "sha256:16286214e405c211fc774187f3e4bbb1351290b8dfb88e8948af209ce85b719e", size = 34344, upload-time = "2025-06-10T08:55:32.25Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4b/7f/15f014fb195da6c2dbb6c71399b8e76824878718e94de6454038488eed28/opentelemetry_proto-1.45.1.tar.gz", hash = "sha256:79e0fb95e4616691a469439238aa9224d75779b3e108e895d1aa125ab29ca77c", size = 46488, upload-time = "2026-10-06T17:33:11.49Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/28/ab/4591bfa54e946350ce8b3f28e5c658fe9785e7cd11e9c11b1671a867822b/opentelemetry_proto-1.34.1-py3-none-any.whl", hash = "sha256:eb4bb5ac27f2562df2d6857fc557b3a481b5e298bc04f94cc68041f00cebcbd2", size = 55692, upload-time = "2025-06-10T08:55:14.904Z" }, + { url = "https://files.pythonhosted.org/packages/ab/9a/42ec8180a769516ae757e893b69736826efceac7332553915b4528a91c6d/opentelemetry_proto-1.45.1-py3-none-any.whl", hash = "sha256:f38e2a8413053c180cd3d2637fbb279673ec2f6a6e09c995aafa2f452c52b46e", size = 72488, upload-time = "2026-10-06T17:32:53.057Z" }, ] [[package]] name = "opentelemetry-sdk" -version = "1.34.1" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, { name = "opentelemetry-semantic-conventions" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6f/41/fe20f9036433da8e0fcef568984da4c1d1c771fa072ecd1a4d98779dccdd/opentelemetry_sdk-1.34.1.tar.gz", hash = "sha256:8091db0d763fcd6098d4781bbc80ff0971f94e260739aa6afe6fd379cdf3aa4d", size = 159441, upload-time = "2025-06-10T08:55:33.028Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/79/7392e21a1c8f0c61d90b223e31c7e48cb9d452e91a6b820ad24cca5f23c4/opentelemetry_sdk-1.45.1.tar.gz", hash = "sha256:63d24a6ca645019a631e6a51999c73e93adcac1196ca640b8ae78a7cc4762bf3", size = 218324, upload-time = "2026-10-06T17:33:13.26Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/07/1b/def4fe6aa73f483cabf4c748f4c25070d5f7604dcc8b52e962983491b29e/opentelemetry_sdk-1.34.1-py3-none-any.whl", hash = "sha256:308effad4059562f1d92163c61c8141df649da24ce361827812c40abb2a1e96e", size = 118477, upload-time = "2025-06-10T08:55:16.02Z" }, + { url = "https://files.pythonhosted.org/packages/95/3c/87c42b4bd6dd297536f04cd9383d212ac557ecd49f2cbdcd46da1c9ef5c8/opentelemetry_sdk-1.45.1-py3-none-any.whl", hash = "sha256:c604c11dc429810812348989115fa44bd558772a3d7442afc43d024f2c250ca4", size = 140063, upload-time = "2026-10-06T17:32:55.04Z" }, ] [[package]] name = "opentelemetry-semantic-conventions" -version = "0.55b1" +version = "0.66b1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5d/f0/f33458486da911f47c4aa6db9bda308bb80f3236c111bf848bd870c16b16/opentelemetry_semantic_conventions-0.55b1.tar.gz", hash = "sha256:ef95b1f009159c28d7a7849f5cbc71c4c34c845bb514d66adfdf1b3fff3598b3", size = 119829, upload-time = "2025-06-10T08:55:33.881Z" } +sdist = { url = "https://files.pythonhosted.org/packages/46/e4/dbbfb2a010c4db2224a5114638acede6fe563d33cc20fb1752cebcbe6298/opentelemetry_semantic_conventions-0.66b1.tar.gz", hash = "sha256:497ca63bf383723411e8eaf60c8779e9877633c936bb641080adab59d0eb6ec8", size = 150250, upload-time = "2026-10-06T17:33:14.073Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1a/89/267b0af1b1d0ba828f0e60642b6a5116ac1fd917cde7fc02821627029bd1/opentelemetry_semantic_conventions-0.55b1-py3-none-any.whl", hash = "sha256:5da81dfdf7d52e3d37f8fe88d5e771e191de924cfff5f550ab0b8f7b2409baed", size = 196223, upload-time = "2025-06-10T08:55:17.638Z" }, + { url = "https://files.pythonhosted.org/packages/bc/14/67f8aa798857f8cf686f515bf93d9bb877ce952ddc8efae0fa25b45ce0d6/opentelemetry_semantic_conventions-0.66b1-py3-none-any.whl", hash = "sha256:d4cddeb4315490b35213f55e2bdc9ac54bb1e4d318927475bed62b35545e581b", size = 206279, upload-time = "2026-10-06T17:32:56.103Z" }, ] [[package]] diff --git a/examples/example-ai-dedalus/uv.lock b/examples/example-ai-dedalus/uv.lock index 87710ad7..2dfb0a29 100644 --- a/examples/example-ai-dedalus/uv.lock +++ b/examples/example-ai-dedalus/uv.lock @@ -262,18 +262,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/57/b0/0e52c878c53f245edd3a11020f20979b3f490f245af532c7cae3027754b5/idna-3.19-py3-none-any.whl", hash = "sha256:815e7be7a7806d54abb586dc943addc79e8b2ee16915059658cbeff4b1b43bf4", size = 68550, upload-time = "2026-08-18T05:14:22.343Z" }, ] -[[package]] -name = "importlib-metadata" -version = "8.7.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "zipp" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/f3/49/3b30cad09e7771a4982d9975a8cbf64f00d4a1ececb53297f1d9a7be1b10/importlib_metadata-8.7.1.tar.gz", hash = "sha256:49fef1ae6440c182052f407c8d34a68f72efc36db9ca90dc0113398f2fdde8bb", size = 57107, upload-time = "2025-12-21T10:00:19.278Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fa/5e/f8e9a1d23b9c20a551a8a02ea3637b4642e22c2626e3a13a9a29cdea99eb/importlib_metadata-8.7.1-py3-none-any.whl", hash = "sha256:5a1f80bf1daa489495071efbb095d75a634cf28a8bc299581244063b53176151", size = 27865, upload-time = "2025-12-21T10:00:18.329Z" }, -] - [[package]] name = "jiter" version = "0.13.0" @@ -392,50 +380,80 @@ wheels = [ [[package]] name = "opentelemetry-api" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "importlib-metadata" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2c/1d/4049a9e8698361cc1a1aa03a6c59e4fa4c71e0c0f94a30f988a6876a2ae6/opentelemetry_api-1.40.0.tar.gz", hash = "sha256:159be641c0b04d11e9ecd576906462773eb97ae1b657730f0ecf64d32071569f", size = 70851, upload-time = "2026-03-04T14:17:21.555Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2e/02/6e0ae9cc61bd3169d401077b507b3ebc344745171e1051ab430be012dcd9/opentelemetry_api-1.45.1.tar.gz", hash = "sha256:aa38ed19bcc084ba42782a73255b3582283eced7ad6dddbd6695189e69adfb75", size = 72804, upload-time = "2026-10-06T17:32:58.133Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/41/f7dcf80b81ee8e71c1a2b59f14208bc723edbd89ed027a73b175abf6348e/opentelemetry_api-1.45.1-py3-none-any.whl", hash = "sha256:b31553efa588ae44bc306f863c785c5333a9ecc091248c6ee68b4b6c87fdedfb", size = 60256, upload-time = "2026-10-06T17:32:33.506Z" }, +] + +[[package]] +name = "opentelemetry-exporter-http-transport" +version = "0.66b1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/62/0c/e3ebdb4b507f66afcc905e6885a4946969bd75b45988492643356fbbdc63/opentelemetry_exporter_http_transport-0.66b1.tar.gz", hash = "sha256:443080203bf52586ce0b2ad901e8951c61833eab1aa539ae6f1f16fe9e8e7952", size = 11693, upload-time = "2026-10-06T17:32:59.65Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/69/6af86ff66492b481c6a4c05dcfd68beb47ed8ba046440a26a2aac76b95c7/opentelemetry_exporter_http_transport-0.66b1-py3-none-any.whl", hash = "sha256:2f95404bdee7f9d2d529c7de56c7bd86d014d774d8fbf137810e0167f8a492bf", size = 12155, upload-time = "2026-10-06T17:32:35.454Z" }, +] + +[package.optional-dependencies] +requests = [ + { name = "requests" }, +] + +[[package]] +name = "opentelemetry-exporter-otlp-common" +version = "0.66b1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-sdk" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cb/19/41de712173f43057e4532d42ece7d0c6d4210d353e5752433cb14987643f/opentelemetry_exporter_otlp_common-0.66b1.tar.gz", hash = "sha256:6b1403487a2185ac1feb45fd5546fdf8630ce71c36bcefaadf51e2130e9e23f9", size = 14325, upload-time = "2026-10-06T17:33:01.725Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5f/bf/93795954016c522008da367da292adceed71cca6ee1717e1d64c83089099/opentelemetry_api-1.40.0-py3-none-any.whl", hash = "sha256:82dd69331ae74b06f6a874704be0cfaa49a1650e1537d4a813b86ecef7d0ecf9", size = 68676, upload-time = "2026-03-04T14:17:01.24Z" }, + { url = "https://files.pythonhosted.org/packages/fc/39/8c23d67665c762aa51840fa06f86e902e8f6f1693bc8d7e3d98cd6e2f753/opentelemetry_exporter_otlp_common-0.66b1-py3-none-any.whl", hash = "sha256:00ff8592c3a7cb729ff3fdc7ffa12372c243bdf2163e80c180994d0c7bd83ee9", size = 12385, upload-time = "2026-10-06T17:32:38.177Z" }, ] [[package]] name = "opentelemetry-exporter-otlp-proto-common" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-proto" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/51/bc/1559d46557fe6eca0b46c88d4c2676285f1f3be2e8d06bb5d15fbffc814a/opentelemetry_exporter_otlp_proto_common-1.40.0.tar.gz", hash = "sha256:1cbee86a4064790b362a86601ee7934f368b81cd4cc2f2e163902a6e7818a0fa", size = 20416, upload-time = "2026-03-04T14:17:23.801Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c1/8e/65e85e5137991a3c493b11682151d198638a5bc1dd4b4c5f67e013c57d7c/opentelemetry_exporter_otlp_proto_common-1.45.1.tar.gz", hash = "sha256:2e4adcc3a67bcf57804fc49514f0ef64974ca7590aa3491da389852b4a0628f6", size = 18873, upload-time = "2026-10-06T17:33:04.471Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8b/ca/8f122055c97a932311a3f640273f084e738008933503d0c2563cd5d591fc/opentelemetry_exporter_otlp_proto_common-1.40.0-py3-none-any.whl", hash = "sha256:7081ff453835a82417bf38dccf122c827c3cbc94f2079b03bba02a3165f25149", size = 18369, upload-time = "2026-03-04T14:17:04.796Z" }, + { url = "https://files.pythonhosted.org/packages/84/aa/92f225d353904e7f70b8b3e3c1b02db0cf56f744c2e83c581dc372e78873/opentelemetry_exporter_otlp_proto_common-1.45.1-py3-none-any.whl", hash = "sha256:2f446183ae7047b036226f1d846c41a834b0e8755ad13b51a51dd38952eb466c", size = 15393, upload-time = "2026-10-06T17:32:41.911Z" }, ] [[package]] name = "opentelemetry-exporter-otlp-proto-http" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "googleapis-common-protos" }, { name = "opentelemetry-api" }, + { name = "opentelemetry-exporter-http-transport", extra = ["requests"] }, + { name = "opentelemetry-exporter-otlp-common" }, { name = "opentelemetry-exporter-otlp-proto-common" }, { name = "opentelemetry-proto" }, { name = "opentelemetry-sdk" }, { name = "requests" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2e/fa/73d50e2c15c56be4d000c98e24221d494674b0cc95524e2a8cb3856d95a4/opentelemetry_exporter_otlp_proto_http-1.40.0.tar.gz", hash = "sha256:db48f5e0f33217588bbc00274a31517ba830da576e59503507c839b38fa0869c", size = 17772, upload-time = "2026-03-04T14:17:25.324Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/17/26487707ea4caa97b17e6e4b5fa72133a53512ffa2f5cf7a49ef284b29cb/opentelemetry_exporter_otlp_proto_http-1.45.1.tar.gz", hash = "sha256:45c218405ce3fd879596924b1874bf9a8f6880206d61065c5a912c8e5c297fb7", size = 28839, upload-time = "2026-10-06T17:33:05.713Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/3a/8865d6754e61c9fb170cdd530a124a53769ee5f740236064816eb0ca7301/opentelemetry_exporter_otlp_proto_http-1.40.0-py3-none-any.whl", hash = "sha256:a8d1dab28f504c5d96577d6509f80a8150e44e8f45f82cdbe0e34c99ab040069", size = 19960, upload-time = "2026-03-04T14:17:07.153Z" }, + { url = "https://files.pythonhosted.org/packages/aa/1f/517eaa0187ba106a9da97160ce2add3a371812681dc440930b267f714e42/opentelemetry_exporter_otlp_proto_http-1.45.1-py3-none-any.whl", hash = "sha256:24a97cf3753c7fb52fad44a696e452ff371686339e2acf3309e2eda3d0230700", size = 22180, upload-time = "2026-10-06T17:32:43.946Z" }, ] [[package]] name = "opentelemetry-instrumentation" -version = "0.61b0" +version = "0.66b1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, @@ -443,9 +461,9 @@ dependencies = [ { name = "packaging" }, { name = "wrapt" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/da/37/6bf8e66bfcee5d3c6515b79cb2ee9ad05fe573c20f7ceb288d0e7eeec28c/opentelemetry_instrumentation-0.61b0.tar.gz", hash = "sha256:cb21b48db738c9de196eba6b805b4ff9de3b7f187e4bbf9a466fa170514f1fc7", size = 32606, upload-time = "2026-03-04T14:20:16.825Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a5/03/89e47ff8d52a4f83b343e6eb9ef1698ff45357216e5b6b2b21e0da5c5c7d/opentelemetry_instrumentation-0.66b1.tar.gz", hash = "sha256:e79a510f7d87c72d95e964ddb42193a0d9a75668c027d980eab032ea1322a5ce", size = 43308, upload-time = "2026-10-06T17:36:10.703Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d8/3e/f6f10f178b6316de67f0dfdbbb699a24fbe8917cf1743c1595fb9dcdd461/opentelemetry_instrumentation-0.61b0-py3-none-any.whl", hash = "sha256:92a93a280e69788e8f88391247cc530fd81f16f2b011979d4d6398f805cfbc63", size = 33448, upload-time = "2026-03-04T14:19:02.447Z" }, + { url = "https://files.pythonhosted.org/packages/da/b2/d1413681ff43e13ac9860df27e1226d3199ab0b97b352ceea41abcc660a5/opentelemetry_instrumentation-0.66b1-py3-none-any.whl", hash = "sha256:4c4aa14dc9a24a02325a9d4c42c4d0208dbb1374c2b1b8fe6c9392d59f3e1008", size = 36904, upload-time = "2026-10-06T17:35:11.663Z" }, ] [[package]] @@ -464,41 +482,41 @@ wheels = [ [[package]] name = "opentelemetry-proto" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "protobuf" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4c/77/dd38991db037fdfce45849491cb61de5ab000f49824a00230afb112a4392/opentelemetry_proto-1.40.0.tar.gz", hash = "sha256:03f639ca129ba513f5819810f5b1f42bcb371391405d99c168fe6937c62febcd", size = 45667, upload-time = "2026-03-04T14:17:31.194Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4b/7f/15f014fb195da6c2dbb6c71399b8e76824878718e94de6454038488eed28/opentelemetry_proto-1.45.1.tar.gz", hash = "sha256:79e0fb95e4616691a469439238aa9224d75779b3e108e895d1aa125ab29ca77c", size = 46488, upload-time = "2026-10-06T17:33:11.49Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b9/b2/189b2577dde745b15625b3214302605b1353436219d42b7912e77fa8dc24/opentelemetry_proto-1.40.0-py3-none-any.whl", hash = "sha256:266c4385d88923a23d63e353e9761af0f47a6ed0d486979777fe4de59dc9b25f", size = 72073, upload-time = "2026-03-04T14:17:16.673Z" }, + { url = "https://files.pythonhosted.org/packages/ab/9a/42ec8180a769516ae757e893b69736826efceac7332553915b4528a91c6d/opentelemetry_proto-1.45.1-py3-none-any.whl", hash = "sha256:f38e2a8413053c180cd3d2637fbb279673ec2f6a6e09c995aafa2f452c52b46e", size = 72488, upload-time = "2026-10-06T17:32:53.057Z" }, ] [[package]] name = "opentelemetry-sdk" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, { name = "opentelemetry-semantic-conventions" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/58/fd/3c3125b20ba18ce2155ba9ea74acb0ae5d25f8cd39cfd37455601b7955cc/opentelemetry_sdk-1.40.0.tar.gz", hash = "sha256:18e9f5ec20d859d268c7cb3c5198c8d105d073714db3de50b593b8c1345a48f2", size = 184252, upload-time = "2026-03-04T14:17:31.87Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/79/7392e21a1c8f0c61d90b223e31c7e48cb9d452e91a6b820ad24cca5f23c4/opentelemetry_sdk-1.45.1.tar.gz", hash = "sha256:63d24a6ca645019a631e6a51999c73e93adcac1196ca640b8ae78a7cc4762bf3", size = 218324, upload-time = "2026-10-06T17:33:13.26Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/c5/6a852903d8bfac758c6dc6e9a68b015d3c33f2f1be5e9591e0f4b69c7e0a/opentelemetry_sdk-1.40.0-py3-none-any.whl", hash = "sha256:787d2154a71f4b3d81f20524a8ce061b7db667d24e46753f32a7bc48f1c1f3f1", size = 141951, upload-time = "2026-03-04T14:17:17.961Z" }, + { url = "https://files.pythonhosted.org/packages/95/3c/87c42b4bd6dd297536f04cd9383d212ac557ecd49f2cbdcd46da1c9ef5c8/opentelemetry_sdk-1.45.1-py3-none-any.whl", hash = "sha256:c604c11dc429810812348989115fa44bd558772a3d7442afc43d024f2c250ca4", size = 140063, upload-time = "2026-10-06T17:32:55.04Z" }, ] [[package]] name = "opentelemetry-semantic-conventions" -version = "0.61b0" +version = "0.66b1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6d/c0/4ae7973f3c2cfd2b6e321f1675626f0dab0a97027cc7a297474c9c8f3d04/opentelemetry_semantic_conventions-0.61b0.tar.gz", hash = "sha256:072f65473c5d7c6dc0355b27d6c9d1a679d63b6d4b4b16a9773062cb7e31192a", size = 145755, upload-time = "2026-03-04T14:17:32.664Z" } +sdist = { url = "https://files.pythonhosted.org/packages/46/e4/dbbfb2a010c4db2224a5114638acede6fe563d33cc20fb1752cebcbe6298/opentelemetry_semantic_conventions-0.66b1.tar.gz", hash = "sha256:497ca63bf383723411e8eaf60c8779e9877633c936bb641080adab59d0eb6ec8", size = 150250, upload-time = "2026-10-06T17:33:14.073Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b2/37/cc6a55e448deaa9b27377d087da8615a3416d8ad523d5960b78dbeadd02a/opentelemetry_semantic_conventions-0.61b0-py3-none-any.whl", hash = "sha256:fa530a96be229795f8cef353739b618148b0fe2b4b3f005e60e262926c4d38e2", size = 231621, upload-time = "2026-03-04T14:17:19.33Z" }, + { url = "https://files.pythonhosted.org/packages/bc/14/67f8aa798857f8cf686f515bf93d9bb877ce952ddc8efae0fa25b45ce0d6/opentelemetry_semantic_conventions-0.66b1-py3-none-any.whl", hash = "sha256:d4cddeb4315490b35213f55e2bdc9ac54bb1e4d318927475bed62b35545e581b", size = 206279, upload-time = "2026-10-06T17:32:56.103Z" }, ] [[package]] @@ -512,7 +530,7 @@ wheels = [ [[package]] name = "posthog" -version = "7.75.4" +version = "7.75.11" source = { directory = "../../" } dependencies = [ { name = "backoff" }, @@ -877,12 +895,3 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/46/78/10ad9781128ed2f99dbc474f43283b13fea8ba58723e98844367531c18e9/wrapt-1.17.3-cp314-cp314t-win_arm64.whl", hash = "sha256:f38e60678850c42461d4202739f9bf1e3a737c7ad283638251e79cc49effb6b6", size = 38471, upload-time = "2025-08-12T05:52:57.784Z" }, { url = "https://files.pythonhosted.org/packages/1f/f6/a933bd70f98e9cf3e08167fc5cd7aaaca49147e48411c0bd5ae701bb2194/wrapt-1.17.3-py3-none-any.whl", hash = "sha256:7171ae35d2c33d326ac19dd8facb1e82e5fd04ef8c6c0e394d7af55a55051c22", size = 23591, upload-time = "2025-08-12T05:53:20.674Z" }, ] - -[[package]] -name = "zipp" -version = "3.23.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166", size = 25547, upload-time = "2025-06-08T17:06:39.4Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276, upload-time = "2025-06-08T17:06:38.034Z" }, -] diff --git a/examples/example-ai-deepseek/uv.lock b/examples/example-ai-deepseek/uv.lock index bd66d5ec..7dd79a93 100644 --- a/examples/example-ai-deepseek/uv.lock +++ b/examples/example-ai-deepseek/uv.lock @@ -262,18 +262,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/57/b0/0e52c878c53f245edd3a11020f20979b3f490f245af532c7cae3027754b5/idna-3.19-py3-none-any.whl", hash = "sha256:815e7be7a7806d54abb586dc943addc79e8b2ee16915059658cbeff4b1b43bf4", size = 68550, upload-time = "2026-08-18T05:14:22.343Z" }, ] -[[package]] -name = "importlib-metadata" -version = "8.7.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "zipp" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/f3/49/3b30cad09e7771a4982d9975a8cbf64f00d4a1ececb53297f1d9a7be1b10/importlib_metadata-8.7.1.tar.gz", hash = "sha256:49fef1ae6440c182052f407c8d34a68f72efc36db9ca90dc0113398f2fdde8bb", size = 57107, upload-time = "2025-12-21T10:00:19.278Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fa/5e/f8e9a1d23b9c20a551a8a02ea3637b4642e22c2626e3a13a9a29cdea99eb/importlib_metadata-8.7.1-py3-none-any.whl", hash = "sha256:5a1f80bf1daa489495071efbb095d75a634cf28a8bc299581244063b53176151", size = 27865, upload-time = "2025-12-21T10:00:18.329Z" }, -] - [[package]] name = "jiter" version = "0.13.0" @@ -392,50 +380,80 @@ wheels = [ [[package]] name = "opentelemetry-api" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "importlib-metadata" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2c/1d/4049a9e8698361cc1a1aa03a6c59e4fa4c71e0c0f94a30f988a6876a2ae6/opentelemetry_api-1.40.0.tar.gz", hash = "sha256:159be641c0b04d11e9ecd576906462773eb97ae1b657730f0ecf64d32071569f", size = 70851, upload-time = "2026-03-04T14:17:21.555Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2e/02/6e0ae9cc61bd3169d401077b507b3ebc344745171e1051ab430be012dcd9/opentelemetry_api-1.45.1.tar.gz", hash = "sha256:aa38ed19bcc084ba42782a73255b3582283eced7ad6dddbd6695189e69adfb75", size = 72804, upload-time = "2026-10-06T17:32:58.133Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/41/f7dcf80b81ee8e71c1a2b59f14208bc723edbd89ed027a73b175abf6348e/opentelemetry_api-1.45.1-py3-none-any.whl", hash = "sha256:b31553efa588ae44bc306f863c785c5333a9ecc091248c6ee68b4b6c87fdedfb", size = 60256, upload-time = "2026-10-06T17:32:33.506Z" }, +] + +[[package]] +name = "opentelemetry-exporter-http-transport" +version = "0.66b1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/62/0c/e3ebdb4b507f66afcc905e6885a4946969bd75b45988492643356fbbdc63/opentelemetry_exporter_http_transport-0.66b1.tar.gz", hash = "sha256:443080203bf52586ce0b2ad901e8951c61833eab1aa539ae6f1f16fe9e8e7952", size = 11693, upload-time = "2026-10-06T17:32:59.65Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/69/6af86ff66492b481c6a4c05dcfd68beb47ed8ba046440a26a2aac76b95c7/opentelemetry_exporter_http_transport-0.66b1-py3-none-any.whl", hash = "sha256:2f95404bdee7f9d2d529c7de56c7bd86d014d774d8fbf137810e0167f8a492bf", size = 12155, upload-time = "2026-10-06T17:32:35.454Z" }, +] + +[package.optional-dependencies] +requests = [ + { name = "requests" }, +] + +[[package]] +name = "opentelemetry-exporter-otlp-common" +version = "0.66b1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-sdk" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cb/19/41de712173f43057e4532d42ece7d0c6d4210d353e5752433cb14987643f/opentelemetry_exporter_otlp_common-0.66b1.tar.gz", hash = "sha256:6b1403487a2185ac1feb45fd5546fdf8630ce71c36bcefaadf51e2130e9e23f9", size = 14325, upload-time = "2026-10-06T17:33:01.725Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5f/bf/93795954016c522008da367da292adceed71cca6ee1717e1d64c83089099/opentelemetry_api-1.40.0-py3-none-any.whl", hash = "sha256:82dd69331ae74b06f6a874704be0cfaa49a1650e1537d4a813b86ecef7d0ecf9", size = 68676, upload-time = "2026-03-04T14:17:01.24Z" }, + { url = "https://files.pythonhosted.org/packages/fc/39/8c23d67665c762aa51840fa06f86e902e8f6f1693bc8d7e3d98cd6e2f753/opentelemetry_exporter_otlp_common-0.66b1-py3-none-any.whl", hash = "sha256:00ff8592c3a7cb729ff3fdc7ffa12372c243bdf2163e80c180994d0c7bd83ee9", size = 12385, upload-time = "2026-10-06T17:32:38.177Z" }, ] [[package]] name = "opentelemetry-exporter-otlp-proto-common" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-proto" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/51/bc/1559d46557fe6eca0b46c88d4c2676285f1f3be2e8d06bb5d15fbffc814a/opentelemetry_exporter_otlp_proto_common-1.40.0.tar.gz", hash = "sha256:1cbee86a4064790b362a86601ee7934f368b81cd4cc2f2e163902a6e7818a0fa", size = 20416, upload-time = "2026-03-04T14:17:23.801Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c1/8e/65e85e5137991a3c493b11682151d198638a5bc1dd4b4c5f67e013c57d7c/opentelemetry_exporter_otlp_proto_common-1.45.1.tar.gz", hash = "sha256:2e4adcc3a67bcf57804fc49514f0ef64974ca7590aa3491da389852b4a0628f6", size = 18873, upload-time = "2026-10-06T17:33:04.471Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8b/ca/8f122055c97a932311a3f640273f084e738008933503d0c2563cd5d591fc/opentelemetry_exporter_otlp_proto_common-1.40.0-py3-none-any.whl", hash = "sha256:7081ff453835a82417bf38dccf122c827c3cbc94f2079b03bba02a3165f25149", size = 18369, upload-time = "2026-03-04T14:17:04.796Z" }, + { url = "https://files.pythonhosted.org/packages/84/aa/92f225d353904e7f70b8b3e3c1b02db0cf56f744c2e83c581dc372e78873/opentelemetry_exporter_otlp_proto_common-1.45.1-py3-none-any.whl", hash = "sha256:2f446183ae7047b036226f1d846c41a834b0e8755ad13b51a51dd38952eb466c", size = 15393, upload-time = "2026-10-06T17:32:41.911Z" }, ] [[package]] name = "opentelemetry-exporter-otlp-proto-http" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "googleapis-common-protos" }, { name = "opentelemetry-api" }, + { name = "opentelemetry-exporter-http-transport", extra = ["requests"] }, + { name = "opentelemetry-exporter-otlp-common" }, { name = "opentelemetry-exporter-otlp-proto-common" }, { name = "opentelemetry-proto" }, { name = "opentelemetry-sdk" }, { name = "requests" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2e/fa/73d50e2c15c56be4d000c98e24221d494674b0cc95524e2a8cb3856d95a4/opentelemetry_exporter_otlp_proto_http-1.40.0.tar.gz", hash = "sha256:db48f5e0f33217588bbc00274a31517ba830da576e59503507c839b38fa0869c", size = 17772, upload-time = "2026-03-04T14:17:25.324Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/17/26487707ea4caa97b17e6e4b5fa72133a53512ffa2f5cf7a49ef284b29cb/opentelemetry_exporter_otlp_proto_http-1.45.1.tar.gz", hash = "sha256:45c218405ce3fd879596924b1874bf9a8f6880206d61065c5a912c8e5c297fb7", size = 28839, upload-time = "2026-10-06T17:33:05.713Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/3a/8865d6754e61c9fb170cdd530a124a53769ee5f740236064816eb0ca7301/opentelemetry_exporter_otlp_proto_http-1.40.0-py3-none-any.whl", hash = "sha256:a8d1dab28f504c5d96577d6509f80a8150e44e8f45f82cdbe0e34c99ab040069", size = 19960, upload-time = "2026-03-04T14:17:07.153Z" }, + { url = "https://files.pythonhosted.org/packages/aa/1f/517eaa0187ba106a9da97160ce2add3a371812681dc440930b267f714e42/opentelemetry_exporter_otlp_proto_http-1.45.1-py3-none-any.whl", hash = "sha256:24a97cf3753c7fb52fad44a696e452ff371686339e2acf3309e2eda3d0230700", size = 22180, upload-time = "2026-10-06T17:32:43.946Z" }, ] [[package]] name = "opentelemetry-instrumentation" -version = "0.61b0" +version = "0.66b1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, @@ -443,9 +461,9 @@ dependencies = [ { name = "packaging" }, { name = "wrapt" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/da/37/6bf8e66bfcee5d3c6515b79cb2ee9ad05fe573c20f7ceb288d0e7eeec28c/opentelemetry_instrumentation-0.61b0.tar.gz", hash = "sha256:cb21b48db738c9de196eba6b805b4ff9de3b7f187e4bbf9a466fa170514f1fc7", size = 32606, upload-time = "2026-03-04T14:20:16.825Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a5/03/89e47ff8d52a4f83b343e6eb9ef1698ff45357216e5b6b2b21e0da5c5c7d/opentelemetry_instrumentation-0.66b1.tar.gz", hash = "sha256:e79a510f7d87c72d95e964ddb42193a0d9a75668c027d980eab032ea1322a5ce", size = 43308, upload-time = "2026-10-06T17:36:10.703Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d8/3e/f6f10f178b6316de67f0dfdbbb699a24fbe8917cf1743c1595fb9dcdd461/opentelemetry_instrumentation-0.61b0-py3-none-any.whl", hash = "sha256:92a93a280e69788e8f88391247cc530fd81f16f2b011979d4d6398f805cfbc63", size = 33448, upload-time = "2026-03-04T14:19:02.447Z" }, + { url = "https://files.pythonhosted.org/packages/da/b2/d1413681ff43e13ac9860df27e1226d3199ab0b97b352ceea41abcc660a5/opentelemetry_instrumentation-0.66b1-py3-none-any.whl", hash = "sha256:4c4aa14dc9a24a02325a9d4c42c4d0208dbb1374c2b1b8fe6c9392d59f3e1008", size = 36904, upload-time = "2026-10-06T17:35:11.663Z" }, ] [[package]] @@ -464,41 +482,41 @@ wheels = [ [[package]] name = "opentelemetry-proto" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "protobuf" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4c/77/dd38991db037fdfce45849491cb61de5ab000f49824a00230afb112a4392/opentelemetry_proto-1.40.0.tar.gz", hash = "sha256:03f639ca129ba513f5819810f5b1f42bcb371391405d99c168fe6937c62febcd", size = 45667, upload-time = "2026-03-04T14:17:31.194Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4b/7f/15f014fb195da6c2dbb6c71399b8e76824878718e94de6454038488eed28/opentelemetry_proto-1.45.1.tar.gz", hash = "sha256:79e0fb95e4616691a469439238aa9224d75779b3e108e895d1aa125ab29ca77c", size = 46488, upload-time = "2026-10-06T17:33:11.49Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b9/b2/189b2577dde745b15625b3214302605b1353436219d42b7912e77fa8dc24/opentelemetry_proto-1.40.0-py3-none-any.whl", hash = "sha256:266c4385d88923a23d63e353e9761af0f47a6ed0d486979777fe4de59dc9b25f", size = 72073, upload-time = "2026-03-04T14:17:16.673Z" }, + { url = "https://files.pythonhosted.org/packages/ab/9a/42ec8180a769516ae757e893b69736826efceac7332553915b4528a91c6d/opentelemetry_proto-1.45.1-py3-none-any.whl", hash = "sha256:f38e2a8413053c180cd3d2637fbb279673ec2f6a6e09c995aafa2f452c52b46e", size = 72488, upload-time = "2026-10-06T17:32:53.057Z" }, ] [[package]] name = "opentelemetry-sdk" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, { name = "opentelemetry-semantic-conventions" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/58/fd/3c3125b20ba18ce2155ba9ea74acb0ae5d25f8cd39cfd37455601b7955cc/opentelemetry_sdk-1.40.0.tar.gz", hash = "sha256:18e9f5ec20d859d268c7cb3c5198c8d105d073714db3de50b593b8c1345a48f2", size = 184252, upload-time = "2026-03-04T14:17:31.87Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/79/7392e21a1c8f0c61d90b223e31c7e48cb9d452e91a6b820ad24cca5f23c4/opentelemetry_sdk-1.45.1.tar.gz", hash = "sha256:63d24a6ca645019a631e6a51999c73e93adcac1196ca640b8ae78a7cc4762bf3", size = 218324, upload-time = "2026-10-06T17:33:13.26Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/c5/6a852903d8bfac758c6dc6e9a68b015d3c33f2f1be5e9591e0f4b69c7e0a/opentelemetry_sdk-1.40.0-py3-none-any.whl", hash = "sha256:787d2154a71f4b3d81f20524a8ce061b7db667d24e46753f32a7bc48f1c1f3f1", size = 141951, upload-time = "2026-03-04T14:17:17.961Z" }, + { url = "https://files.pythonhosted.org/packages/95/3c/87c42b4bd6dd297536f04cd9383d212ac557ecd49f2cbdcd46da1c9ef5c8/opentelemetry_sdk-1.45.1-py3-none-any.whl", hash = "sha256:c604c11dc429810812348989115fa44bd558772a3d7442afc43d024f2c250ca4", size = 140063, upload-time = "2026-10-06T17:32:55.04Z" }, ] [[package]] name = "opentelemetry-semantic-conventions" -version = "0.61b0" +version = "0.66b1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6d/c0/4ae7973f3c2cfd2b6e321f1675626f0dab0a97027cc7a297474c9c8f3d04/opentelemetry_semantic_conventions-0.61b0.tar.gz", hash = "sha256:072f65473c5d7c6dc0355b27d6c9d1a679d63b6d4b4b16a9773062cb7e31192a", size = 145755, upload-time = "2026-03-04T14:17:32.664Z" } +sdist = { url = "https://files.pythonhosted.org/packages/46/e4/dbbfb2a010c4db2224a5114638acede6fe563d33cc20fb1752cebcbe6298/opentelemetry_semantic_conventions-0.66b1.tar.gz", hash = "sha256:497ca63bf383723411e8eaf60c8779e9877633c936bb641080adab59d0eb6ec8", size = 150250, upload-time = "2026-10-06T17:33:14.073Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b2/37/cc6a55e448deaa9b27377d087da8615a3416d8ad523d5960b78dbeadd02a/opentelemetry_semantic_conventions-0.61b0-py3-none-any.whl", hash = "sha256:fa530a96be229795f8cef353739b618148b0fe2b4b3f005e60e262926c4d38e2", size = 231621, upload-time = "2026-03-04T14:17:19.33Z" }, + { url = "https://files.pythonhosted.org/packages/bc/14/67f8aa798857f8cf686f515bf93d9bb877ce952ddc8efae0fa25b45ce0d6/opentelemetry_semantic_conventions-0.66b1-py3-none-any.whl", hash = "sha256:d4cddeb4315490b35213f55e2bdc9ac54bb1e4d318927475bed62b35545e581b", size = 206279, upload-time = "2026-10-06T17:32:56.103Z" }, ] [[package]] @@ -512,7 +530,7 @@ wheels = [ [[package]] name = "posthog" -version = "7.75.4" +version = "7.75.11" source = { directory = "../../" } dependencies = [ { name = "backoff" }, @@ -877,12 +895,3 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/46/78/10ad9781128ed2f99dbc474f43283b13fea8ba58723e98844367531c18e9/wrapt-1.17.3-cp314-cp314t-win_arm64.whl", hash = "sha256:f38e60678850c42461d4202739f9bf1e3a737c7ad283638251e79cc49effb6b6", size = 38471, upload-time = "2025-08-12T05:52:57.784Z" }, { url = "https://files.pythonhosted.org/packages/1f/f6/a933bd70f98e9cf3e08167fc5cd7aaaca49147e48411c0bd5ae701bb2194/wrapt-1.17.3-py3-none-any.whl", hash = "sha256:7171ae35d2c33d326ac19dd8facb1e82e5fd04ef8c6c0e394d7af55a55051c22", size = 23591, upload-time = "2025-08-12T05:53:20.674Z" }, ] - -[[package]] -name = "zipp" -version = "3.23.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166", size = 25547, upload-time = "2025-06-08T17:06:39.4Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276, upload-time = "2025-06-08T17:06:38.034Z" }, -] diff --git a/examples/example-ai-fireworks-ai/uv.lock b/examples/example-ai-fireworks-ai/uv.lock index 978ae3f3..2a573ab2 100644 --- a/examples/example-ai-fireworks-ai/uv.lock +++ b/examples/example-ai-fireworks-ai/uv.lock @@ -262,18 +262,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/57/b0/0e52c878c53f245edd3a11020f20979b3f490f245af532c7cae3027754b5/idna-3.19-py3-none-any.whl", hash = "sha256:815e7be7a7806d54abb586dc943addc79e8b2ee16915059658cbeff4b1b43bf4", size = 68550, upload-time = "2026-08-18T05:14:22.343Z" }, ] -[[package]] -name = "importlib-metadata" -version = "8.7.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "zipp" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/f3/49/3b30cad09e7771a4982d9975a8cbf64f00d4a1ececb53297f1d9a7be1b10/importlib_metadata-8.7.1.tar.gz", hash = "sha256:49fef1ae6440c182052f407c8d34a68f72efc36db9ca90dc0113398f2fdde8bb", size = 57107, upload-time = "2025-12-21T10:00:19.278Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fa/5e/f8e9a1d23b9c20a551a8a02ea3637b4642e22c2626e3a13a9a29cdea99eb/importlib_metadata-8.7.1-py3-none-any.whl", hash = "sha256:5a1f80bf1daa489495071efbb095d75a634cf28a8bc299581244063b53176151", size = 27865, upload-time = "2025-12-21T10:00:18.329Z" }, -] - [[package]] name = "jiter" version = "0.13.0" @@ -392,50 +380,80 @@ wheels = [ [[package]] name = "opentelemetry-api" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "importlib-metadata" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2c/1d/4049a9e8698361cc1a1aa03a6c59e4fa4c71e0c0f94a30f988a6876a2ae6/opentelemetry_api-1.40.0.tar.gz", hash = "sha256:159be641c0b04d11e9ecd576906462773eb97ae1b657730f0ecf64d32071569f", size = 70851, upload-time = "2026-03-04T14:17:21.555Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2e/02/6e0ae9cc61bd3169d401077b507b3ebc344745171e1051ab430be012dcd9/opentelemetry_api-1.45.1.tar.gz", hash = "sha256:aa38ed19bcc084ba42782a73255b3582283eced7ad6dddbd6695189e69adfb75", size = 72804, upload-time = "2026-10-06T17:32:58.133Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/41/f7dcf80b81ee8e71c1a2b59f14208bc723edbd89ed027a73b175abf6348e/opentelemetry_api-1.45.1-py3-none-any.whl", hash = "sha256:b31553efa588ae44bc306f863c785c5333a9ecc091248c6ee68b4b6c87fdedfb", size = 60256, upload-time = "2026-10-06T17:32:33.506Z" }, +] + +[[package]] +name = "opentelemetry-exporter-http-transport" +version = "0.66b1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/62/0c/e3ebdb4b507f66afcc905e6885a4946969bd75b45988492643356fbbdc63/opentelemetry_exporter_http_transport-0.66b1.tar.gz", hash = "sha256:443080203bf52586ce0b2ad901e8951c61833eab1aa539ae6f1f16fe9e8e7952", size = 11693, upload-time = "2026-10-06T17:32:59.65Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/69/6af86ff66492b481c6a4c05dcfd68beb47ed8ba046440a26a2aac76b95c7/opentelemetry_exporter_http_transport-0.66b1-py3-none-any.whl", hash = "sha256:2f95404bdee7f9d2d529c7de56c7bd86d014d774d8fbf137810e0167f8a492bf", size = 12155, upload-time = "2026-10-06T17:32:35.454Z" }, +] + +[package.optional-dependencies] +requests = [ + { name = "requests" }, +] + +[[package]] +name = "opentelemetry-exporter-otlp-common" +version = "0.66b1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-sdk" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cb/19/41de712173f43057e4532d42ece7d0c6d4210d353e5752433cb14987643f/opentelemetry_exporter_otlp_common-0.66b1.tar.gz", hash = "sha256:6b1403487a2185ac1feb45fd5546fdf8630ce71c36bcefaadf51e2130e9e23f9", size = 14325, upload-time = "2026-10-06T17:33:01.725Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5f/bf/93795954016c522008da367da292adceed71cca6ee1717e1d64c83089099/opentelemetry_api-1.40.0-py3-none-any.whl", hash = "sha256:82dd69331ae74b06f6a874704be0cfaa49a1650e1537d4a813b86ecef7d0ecf9", size = 68676, upload-time = "2026-03-04T14:17:01.24Z" }, + { url = "https://files.pythonhosted.org/packages/fc/39/8c23d67665c762aa51840fa06f86e902e8f6f1693bc8d7e3d98cd6e2f753/opentelemetry_exporter_otlp_common-0.66b1-py3-none-any.whl", hash = "sha256:00ff8592c3a7cb729ff3fdc7ffa12372c243bdf2163e80c180994d0c7bd83ee9", size = 12385, upload-time = "2026-10-06T17:32:38.177Z" }, ] [[package]] name = "opentelemetry-exporter-otlp-proto-common" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-proto" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/51/bc/1559d46557fe6eca0b46c88d4c2676285f1f3be2e8d06bb5d15fbffc814a/opentelemetry_exporter_otlp_proto_common-1.40.0.tar.gz", hash = "sha256:1cbee86a4064790b362a86601ee7934f368b81cd4cc2f2e163902a6e7818a0fa", size = 20416, upload-time = "2026-03-04T14:17:23.801Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c1/8e/65e85e5137991a3c493b11682151d198638a5bc1dd4b4c5f67e013c57d7c/opentelemetry_exporter_otlp_proto_common-1.45.1.tar.gz", hash = "sha256:2e4adcc3a67bcf57804fc49514f0ef64974ca7590aa3491da389852b4a0628f6", size = 18873, upload-time = "2026-10-06T17:33:04.471Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8b/ca/8f122055c97a932311a3f640273f084e738008933503d0c2563cd5d591fc/opentelemetry_exporter_otlp_proto_common-1.40.0-py3-none-any.whl", hash = "sha256:7081ff453835a82417bf38dccf122c827c3cbc94f2079b03bba02a3165f25149", size = 18369, upload-time = "2026-03-04T14:17:04.796Z" }, + { url = "https://files.pythonhosted.org/packages/84/aa/92f225d353904e7f70b8b3e3c1b02db0cf56f744c2e83c581dc372e78873/opentelemetry_exporter_otlp_proto_common-1.45.1-py3-none-any.whl", hash = "sha256:2f446183ae7047b036226f1d846c41a834b0e8755ad13b51a51dd38952eb466c", size = 15393, upload-time = "2026-10-06T17:32:41.911Z" }, ] [[package]] name = "opentelemetry-exporter-otlp-proto-http" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "googleapis-common-protos" }, { name = "opentelemetry-api" }, + { name = "opentelemetry-exporter-http-transport", extra = ["requests"] }, + { name = "opentelemetry-exporter-otlp-common" }, { name = "opentelemetry-exporter-otlp-proto-common" }, { name = "opentelemetry-proto" }, { name = "opentelemetry-sdk" }, { name = "requests" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2e/fa/73d50e2c15c56be4d000c98e24221d494674b0cc95524e2a8cb3856d95a4/opentelemetry_exporter_otlp_proto_http-1.40.0.tar.gz", hash = "sha256:db48f5e0f33217588bbc00274a31517ba830da576e59503507c839b38fa0869c", size = 17772, upload-time = "2026-03-04T14:17:25.324Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/17/26487707ea4caa97b17e6e4b5fa72133a53512ffa2f5cf7a49ef284b29cb/opentelemetry_exporter_otlp_proto_http-1.45.1.tar.gz", hash = "sha256:45c218405ce3fd879596924b1874bf9a8f6880206d61065c5a912c8e5c297fb7", size = 28839, upload-time = "2026-10-06T17:33:05.713Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/3a/8865d6754e61c9fb170cdd530a124a53769ee5f740236064816eb0ca7301/opentelemetry_exporter_otlp_proto_http-1.40.0-py3-none-any.whl", hash = "sha256:a8d1dab28f504c5d96577d6509f80a8150e44e8f45f82cdbe0e34c99ab040069", size = 19960, upload-time = "2026-03-04T14:17:07.153Z" }, + { url = "https://files.pythonhosted.org/packages/aa/1f/517eaa0187ba106a9da97160ce2add3a371812681dc440930b267f714e42/opentelemetry_exporter_otlp_proto_http-1.45.1-py3-none-any.whl", hash = "sha256:24a97cf3753c7fb52fad44a696e452ff371686339e2acf3309e2eda3d0230700", size = 22180, upload-time = "2026-10-06T17:32:43.946Z" }, ] [[package]] name = "opentelemetry-instrumentation" -version = "0.61b0" +version = "0.66b1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, @@ -443,9 +461,9 @@ dependencies = [ { name = "packaging" }, { name = "wrapt" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/da/37/6bf8e66bfcee5d3c6515b79cb2ee9ad05fe573c20f7ceb288d0e7eeec28c/opentelemetry_instrumentation-0.61b0.tar.gz", hash = "sha256:cb21b48db738c9de196eba6b805b4ff9de3b7f187e4bbf9a466fa170514f1fc7", size = 32606, upload-time = "2026-03-04T14:20:16.825Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a5/03/89e47ff8d52a4f83b343e6eb9ef1698ff45357216e5b6b2b21e0da5c5c7d/opentelemetry_instrumentation-0.66b1.tar.gz", hash = "sha256:e79a510f7d87c72d95e964ddb42193a0d9a75668c027d980eab032ea1322a5ce", size = 43308, upload-time = "2026-10-06T17:36:10.703Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d8/3e/f6f10f178b6316de67f0dfdbbb699a24fbe8917cf1743c1595fb9dcdd461/opentelemetry_instrumentation-0.61b0-py3-none-any.whl", hash = "sha256:92a93a280e69788e8f88391247cc530fd81f16f2b011979d4d6398f805cfbc63", size = 33448, upload-time = "2026-03-04T14:19:02.447Z" }, + { url = "https://files.pythonhosted.org/packages/da/b2/d1413681ff43e13ac9860df27e1226d3199ab0b97b352ceea41abcc660a5/opentelemetry_instrumentation-0.66b1-py3-none-any.whl", hash = "sha256:4c4aa14dc9a24a02325a9d4c42c4d0208dbb1374c2b1b8fe6c9392d59f3e1008", size = 36904, upload-time = "2026-10-06T17:35:11.663Z" }, ] [[package]] @@ -464,41 +482,41 @@ wheels = [ [[package]] name = "opentelemetry-proto" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "protobuf" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4c/77/dd38991db037fdfce45849491cb61de5ab000f49824a00230afb112a4392/opentelemetry_proto-1.40.0.tar.gz", hash = "sha256:03f639ca129ba513f5819810f5b1f42bcb371391405d99c168fe6937c62febcd", size = 45667, upload-time = "2026-03-04T14:17:31.194Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4b/7f/15f014fb195da6c2dbb6c71399b8e76824878718e94de6454038488eed28/opentelemetry_proto-1.45.1.tar.gz", hash = "sha256:79e0fb95e4616691a469439238aa9224d75779b3e108e895d1aa125ab29ca77c", size = 46488, upload-time = "2026-10-06T17:33:11.49Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b9/b2/189b2577dde745b15625b3214302605b1353436219d42b7912e77fa8dc24/opentelemetry_proto-1.40.0-py3-none-any.whl", hash = "sha256:266c4385d88923a23d63e353e9761af0f47a6ed0d486979777fe4de59dc9b25f", size = 72073, upload-time = "2026-03-04T14:17:16.673Z" }, + { url = "https://files.pythonhosted.org/packages/ab/9a/42ec8180a769516ae757e893b69736826efceac7332553915b4528a91c6d/opentelemetry_proto-1.45.1-py3-none-any.whl", hash = "sha256:f38e2a8413053c180cd3d2637fbb279673ec2f6a6e09c995aafa2f452c52b46e", size = 72488, upload-time = "2026-10-06T17:32:53.057Z" }, ] [[package]] name = "opentelemetry-sdk" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, { name = "opentelemetry-semantic-conventions" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/58/fd/3c3125b20ba18ce2155ba9ea74acb0ae5d25f8cd39cfd37455601b7955cc/opentelemetry_sdk-1.40.0.tar.gz", hash = "sha256:18e9f5ec20d859d268c7cb3c5198c8d105d073714db3de50b593b8c1345a48f2", size = 184252, upload-time = "2026-03-04T14:17:31.87Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/79/7392e21a1c8f0c61d90b223e31c7e48cb9d452e91a6b820ad24cca5f23c4/opentelemetry_sdk-1.45.1.tar.gz", hash = "sha256:63d24a6ca645019a631e6a51999c73e93adcac1196ca640b8ae78a7cc4762bf3", size = 218324, upload-time = "2026-10-06T17:33:13.26Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/c5/6a852903d8bfac758c6dc6e9a68b015d3c33f2f1be5e9591e0f4b69c7e0a/opentelemetry_sdk-1.40.0-py3-none-any.whl", hash = "sha256:787d2154a71f4b3d81f20524a8ce061b7db667d24e46753f32a7bc48f1c1f3f1", size = 141951, upload-time = "2026-03-04T14:17:17.961Z" }, + { url = "https://files.pythonhosted.org/packages/95/3c/87c42b4bd6dd297536f04cd9383d212ac557ecd49f2cbdcd46da1c9ef5c8/opentelemetry_sdk-1.45.1-py3-none-any.whl", hash = "sha256:c604c11dc429810812348989115fa44bd558772a3d7442afc43d024f2c250ca4", size = 140063, upload-time = "2026-10-06T17:32:55.04Z" }, ] [[package]] name = "opentelemetry-semantic-conventions" -version = "0.61b0" +version = "0.66b1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6d/c0/4ae7973f3c2cfd2b6e321f1675626f0dab0a97027cc7a297474c9c8f3d04/opentelemetry_semantic_conventions-0.61b0.tar.gz", hash = "sha256:072f65473c5d7c6dc0355b27d6c9d1a679d63b6d4b4b16a9773062cb7e31192a", size = 145755, upload-time = "2026-03-04T14:17:32.664Z" } +sdist = { url = "https://files.pythonhosted.org/packages/46/e4/dbbfb2a010c4db2224a5114638acede6fe563d33cc20fb1752cebcbe6298/opentelemetry_semantic_conventions-0.66b1.tar.gz", hash = "sha256:497ca63bf383723411e8eaf60c8779e9877633c936bb641080adab59d0eb6ec8", size = 150250, upload-time = "2026-10-06T17:33:14.073Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b2/37/cc6a55e448deaa9b27377d087da8615a3416d8ad523d5960b78dbeadd02a/opentelemetry_semantic_conventions-0.61b0-py3-none-any.whl", hash = "sha256:fa530a96be229795f8cef353739b618148b0fe2b4b3f005e60e262926c4d38e2", size = 231621, upload-time = "2026-03-04T14:17:19.33Z" }, + { url = "https://files.pythonhosted.org/packages/bc/14/67f8aa798857f8cf686f515bf93d9bb877ce952ddc8efae0fa25b45ce0d6/opentelemetry_semantic_conventions-0.66b1-py3-none-any.whl", hash = "sha256:d4cddeb4315490b35213f55e2bdc9ac54bb1e4d318927475bed62b35545e581b", size = 206279, upload-time = "2026-10-06T17:32:56.103Z" }, ] [[package]] @@ -512,7 +530,7 @@ wheels = [ [[package]] name = "posthog" -version = "7.75.4" +version = "7.75.11" source = { directory = "../../" } dependencies = [ { name = "backoff" }, @@ -877,12 +895,3 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/46/78/10ad9781128ed2f99dbc474f43283b13fea8ba58723e98844367531c18e9/wrapt-1.17.3-cp314-cp314t-win_arm64.whl", hash = "sha256:f38e60678850c42461d4202739f9bf1e3a737c7ad283638251e79cc49effb6b6", size = 38471, upload-time = "2025-08-12T05:52:57.784Z" }, { url = "https://files.pythonhosted.org/packages/1f/f6/a933bd70f98e9cf3e08167fc5cd7aaaca49147e48411c0bd5ae701bb2194/wrapt-1.17.3-py3-none-any.whl", hash = "sha256:7171ae35d2c33d326ac19dd8facb1e82e5fd04ef8c6c0e394d7af55a55051c22", size = 23591, upload-time = "2025-08-12T05:53:20.674Z" }, ] - -[[package]] -name = "zipp" -version = "3.23.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166", size = 25547, upload-time = "2025-06-08T17:06:39.4Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276, upload-time = "2025-06-08T17:06:38.034Z" }, -] diff --git a/examples/example-ai-gemini/uv.lock b/examples/example-ai-gemini/uv.lock index f512aff3..0402b990 100644 --- a/examples/example-ai-gemini/uv.lock +++ b/examples/example-ai-gemini/uv.lock @@ -432,63 +432,81 @@ wheels = [ ] [[package]] -name = "importlib-metadata" -version = "8.7.1" +name = "opentelemetry-api" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "zipp" }, + { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f3/49/3b30cad09e7771a4982d9975a8cbf64f00d4a1ececb53297f1d9a7be1b10/importlib_metadata-8.7.1.tar.gz", hash = "sha256:49fef1ae6440c182052f407c8d34a68f72efc36db9ca90dc0113398f2fdde8bb", size = 57107, upload-time = "2025-12-21T10:00:19.278Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2e/02/6e0ae9cc61bd3169d401077b507b3ebc344745171e1051ab430be012dcd9/opentelemetry_api-1.45.1.tar.gz", hash = "sha256:aa38ed19bcc084ba42782a73255b3582283eced7ad6dddbd6695189e69adfb75", size = 72804, upload-time = "2026-10-06T17:32:58.133Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fa/5e/f8e9a1d23b9c20a551a8a02ea3637b4642e22c2626e3a13a9a29cdea99eb/importlib_metadata-8.7.1-py3-none-any.whl", hash = "sha256:5a1f80bf1daa489495071efbb095d75a634cf28a8bc299581244063b53176151", size = 27865, upload-time = "2025-12-21T10:00:18.329Z" }, + { url = "https://files.pythonhosted.org/packages/1e/41/f7dcf80b81ee8e71c1a2b59f14208bc723edbd89ed027a73b175abf6348e/opentelemetry_api-1.45.1-py3-none-any.whl", hash = "sha256:b31553efa588ae44bc306f863c785c5333a9ecc091248c6ee68b4b6c87fdedfb", size = 60256, upload-time = "2026-10-06T17:32:33.506Z" }, ] [[package]] -name = "opentelemetry-api" -version = "1.40.0" +name = "opentelemetry-exporter-http-transport" +version = "0.66b1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "importlib-metadata" }, - { name = "typing-extensions" }, + { name = "opentelemetry-api" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2c/1d/4049a9e8698361cc1a1aa03a6c59e4fa4c71e0c0f94a30f988a6876a2ae6/opentelemetry_api-1.40.0.tar.gz", hash = "sha256:159be641c0b04d11e9ecd576906462773eb97ae1b657730f0ecf64d32071569f", size = 70851, upload-time = "2026-03-04T14:17:21.555Z" } +sdist = { url = "https://files.pythonhosted.org/packages/62/0c/e3ebdb4b507f66afcc905e6885a4946969bd75b45988492643356fbbdc63/opentelemetry_exporter_http_transport-0.66b1.tar.gz", hash = "sha256:443080203bf52586ce0b2ad901e8951c61833eab1aa539ae6f1f16fe9e8e7952", size = 11693, upload-time = "2026-10-06T17:32:59.65Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5f/bf/93795954016c522008da367da292adceed71cca6ee1717e1d64c83089099/opentelemetry_api-1.40.0-py3-none-any.whl", hash = "sha256:82dd69331ae74b06f6a874704be0cfaa49a1650e1537d4a813b86ecef7d0ecf9", size = 68676, upload-time = "2026-03-04T14:17:01.24Z" }, + { url = "https://files.pythonhosted.org/packages/04/69/6af86ff66492b481c6a4c05dcfd68beb47ed8ba046440a26a2aac76b95c7/opentelemetry_exporter_http_transport-0.66b1-py3-none-any.whl", hash = "sha256:2f95404bdee7f9d2d529c7de56c7bd86d014d774d8fbf137810e0167f8a492bf", size = 12155, upload-time = "2026-10-06T17:32:35.454Z" }, +] + +[package.optional-dependencies] +requests = [ + { name = "requests" }, +] + +[[package]] +name = "opentelemetry-exporter-otlp-common" +version = "0.66b1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-sdk" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cb/19/41de712173f43057e4532d42ece7d0c6d4210d353e5752433cb14987643f/opentelemetry_exporter_otlp_common-0.66b1.tar.gz", hash = "sha256:6b1403487a2185ac1feb45fd5546fdf8630ce71c36bcefaadf51e2130e9e23f9", size = 14325, upload-time = "2026-10-06T17:33:01.725Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fc/39/8c23d67665c762aa51840fa06f86e902e8f6f1693bc8d7e3d98cd6e2f753/opentelemetry_exporter_otlp_common-0.66b1-py3-none-any.whl", hash = "sha256:00ff8592c3a7cb729ff3fdc7ffa12372c243bdf2163e80c180994d0c7bd83ee9", size = 12385, upload-time = "2026-10-06T17:32:38.177Z" }, ] [[package]] name = "opentelemetry-exporter-otlp-proto-common" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-proto" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/51/bc/1559d46557fe6eca0b46c88d4c2676285f1f3be2e8d06bb5d15fbffc814a/opentelemetry_exporter_otlp_proto_common-1.40.0.tar.gz", hash = "sha256:1cbee86a4064790b362a86601ee7934f368b81cd4cc2f2e163902a6e7818a0fa", size = 20416, upload-time = "2026-03-04T14:17:23.801Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c1/8e/65e85e5137991a3c493b11682151d198638a5bc1dd4b4c5f67e013c57d7c/opentelemetry_exporter_otlp_proto_common-1.45.1.tar.gz", hash = "sha256:2e4adcc3a67bcf57804fc49514f0ef64974ca7590aa3491da389852b4a0628f6", size = 18873, upload-time = "2026-10-06T17:33:04.471Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8b/ca/8f122055c97a932311a3f640273f084e738008933503d0c2563cd5d591fc/opentelemetry_exporter_otlp_proto_common-1.40.0-py3-none-any.whl", hash = "sha256:7081ff453835a82417bf38dccf122c827c3cbc94f2079b03bba02a3165f25149", size = 18369, upload-time = "2026-03-04T14:17:04.796Z" }, + { url = "https://files.pythonhosted.org/packages/84/aa/92f225d353904e7f70b8b3e3c1b02db0cf56f744c2e83c581dc372e78873/opentelemetry_exporter_otlp_proto_common-1.45.1-py3-none-any.whl", hash = "sha256:2f446183ae7047b036226f1d846c41a834b0e8755ad13b51a51dd38952eb466c", size = 15393, upload-time = "2026-10-06T17:32:41.911Z" }, ] [[package]] name = "opentelemetry-exporter-otlp-proto-http" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "googleapis-common-protos" }, { name = "opentelemetry-api" }, + { name = "opentelemetry-exporter-http-transport", extra = ["requests"] }, + { name = "opentelemetry-exporter-otlp-common" }, { name = "opentelemetry-exporter-otlp-proto-common" }, { name = "opentelemetry-proto" }, { name = "opentelemetry-sdk" }, { name = "requests" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2e/fa/73d50e2c15c56be4d000c98e24221d494674b0cc95524e2a8cb3856d95a4/opentelemetry_exporter_otlp_proto_http-1.40.0.tar.gz", hash = "sha256:db48f5e0f33217588bbc00274a31517ba830da576e59503507c839b38fa0869c", size = 17772, upload-time = "2026-03-04T14:17:25.324Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/17/26487707ea4caa97b17e6e4b5fa72133a53512ffa2f5cf7a49ef284b29cb/opentelemetry_exporter_otlp_proto_http-1.45.1.tar.gz", hash = "sha256:45c218405ce3fd879596924b1874bf9a8f6880206d61065c5a912c8e5c297fb7", size = 28839, upload-time = "2026-10-06T17:33:05.713Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/3a/8865d6754e61c9fb170cdd530a124a53769ee5f740236064816eb0ca7301/opentelemetry_exporter_otlp_proto_http-1.40.0-py3-none-any.whl", hash = "sha256:a8d1dab28f504c5d96577d6509f80a8150e44e8f45f82cdbe0e34c99ab040069", size = 19960, upload-time = "2026-03-04T14:17:07.153Z" }, + { url = "https://files.pythonhosted.org/packages/aa/1f/517eaa0187ba106a9da97160ce2add3a371812681dc440930b267f714e42/opentelemetry_exporter_otlp_proto_http-1.45.1-py3-none-any.whl", hash = "sha256:24a97cf3753c7fb52fad44a696e452ff371686339e2acf3309e2eda3d0230700", size = 22180, upload-time = "2026-10-06T17:32:43.946Z" }, ] [[package]] name = "opentelemetry-instrumentation" -version = "0.61b0" +version = "0.66b1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, @@ -496,9 +514,9 @@ dependencies = [ { name = "packaging" }, { name = "wrapt" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/da/37/6bf8e66bfcee5d3c6515b79cb2ee9ad05fe573c20f7ceb288d0e7eeec28c/opentelemetry_instrumentation-0.61b0.tar.gz", hash = "sha256:cb21b48db738c9de196eba6b805b4ff9de3b7f187e4bbf9a466fa170514f1fc7", size = 32606, upload-time = "2026-03-04T14:20:16.825Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a5/03/89e47ff8d52a4f83b343e6eb9ef1698ff45357216e5b6b2b21e0da5c5c7d/opentelemetry_instrumentation-0.66b1.tar.gz", hash = "sha256:e79a510f7d87c72d95e964ddb42193a0d9a75668c027d980eab032ea1322a5ce", size = 43308, upload-time = "2026-10-06T17:36:10.703Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d8/3e/f6f10f178b6316de67f0dfdbbb699a24fbe8917cf1743c1595fb9dcdd461/opentelemetry_instrumentation-0.61b0-py3-none-any.whl", hash = "sha256:92a93a280e69788e8f88391247cc530fd81f16f2b011979d4d6398f805cfbc63", size = 33448, upload-time = "2026-03-04T14:19:02.447Z" }, + { url = "https://files.pythonhosted.org/packages/da/b2/d1413681ff43e13ac9860df27e1226d3199ab0b97b352ceea41abcc660a5/opentelemetry_instrumentation-0.66b1-py3-none-any.whl", hash = "sha256:4c4aa14dc9a24a02325a9d4c42c4d0208dbb1374c2b1b8fe6c9392d59f3e1008", size = 36904, upload-time = "2026-10-06T17:35:11.663Z" }, ] [[package]] @@ -518,41 +536,41 @@ wheels = [ [[package]] name = "opentelemetry-proto" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "protobuf" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4c/77/dd38991db037fdfce45849491cb61de5ab000f49824a00230afb112a4392/opentelemetry_proto-1.40.0.tar.gz", hash = "sha256:03f639ca129ba513f5819810f5b1f42bcb371391405d99c168fe6937c62febcd", size = 45667, upload-time = "2026-03-04T14:17:31.194Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4b/7f/15f014fb195da6c2dbb6c71399b8e76824878718e94de6454038488eed28/opentelemetry_proto-1.45.1.tar.gz", hash = "sha256:79e0fb95e4616691a469439238aa9224d75779b3e108e895d1aa125ab29ca77c", size = 46488, upload-time = "2026-10-06T17:33:11.49Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b9/b2/189b2577dde745b15625b3214302605b1353436219d42b7912e77fa8dc24/opentelemetry_proto-1.40.0-py3-none-any.whl", hash = "sha256:266c4385d88923a23d63e353e9761af0f47a6ed0d486979777fe4de59dc9b25f", size = 72073, upload-time = "2026-03-04T14:17:16.673Z" }, + { url = "https://files.pythonhosted.org/packages/ab/9a/42ec8180a769516ae757e893b69736826efceac7332553915b4528a91c6d/opentelemetry_proto-1.45.1-py3-none-any.whl", hash = "sha256:f38e2a8413053c180cd3d2637fbb279673ec2f6a6e09c995aafa2f452c52b46e", size = 72488, upload-time = "2026-10-06T17:32:53.057Z" }, ] [[package]] name = "opentelemetry-sdk" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, { name = "opentelemetry-semantic-conventions" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/58/fd/3c3125b20ba18ce2155ba9ea74acb0ae5d25f8cd39cfd37455601b7955cc/opentelemetry_sdk-1.40.0.tar.gz", hash = "sha256:18e9f5ec20d859d268c7cb3c5198c8d105d073714db3de50b593b8c1345a48f2", size = 184252, upload-time = "2026-03-04T14:17:31.87Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/79/7392e21a1c8f0c61d90b223e31c7e48cb9d452e91a6b820ad24cca5f23c4/opentelemetry_sdk-1.45.1.tar.gz", hash = "sha256:63d24a6ca645019a631e6a51999c73e93adcac1196ca640b8ae78a7cc4762bf3", size = 218324, upload-time = "2026-10-06T17:33:13.26Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/c5/6a852903d8bfac758c6dc6e9a68b015d3c33f2f1be5e9591e0f4b69c7e0a/opentelemetry_sdk-1.40.0-py3-none-any.whl", hash = "sha256:787d2154a71f4b3d81f20524a8ce061b7db667d24e46753f32a7bc48f1c1f3f1", size = 141951, upload-time = "2026-03-04T14:17:17.961Z" }, + { url = "https://files.pythonhosted.org/packages/95/3c/87c42b4bd6dd297536f04cd9383d212ac557ecd49f2cbdcd46da1c9ef5c8/opentelemetry_sdk-1.45.1-py3-none-any.whl", hash = "sha256:c604c11dc429810812348989115fa44bd558772a3d7442afc43d024f2c250ca4", size = 140063, upload-time = "2026-10-06T17:32:55.04Z" }, ] [[package]] name = "opentelemetry-semantic-conventions" -version = "0.61b0" +version = "0.66b1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6d/c0/4ae7973f3c2cfd2b6e321f1675626f0dab0a97027cc7a297474c9c8f3d04/opentelemetry_semantic_conventions-0.61b0.tar.gz", hash = "sha256:072f65473c5d7c6dc0355b27d6c9d1a679d63b6d4b4b16a9773062cb7e31192a", size = 145755, upload-time = "2026-03-04T14:17:32.664Z" } +sdist = { url = "https://files.pythonhosted.org/packages/46/e4/dbbfb2a010c4db2224a5114638acede6fe563d33cc20fb1752cebcbe6298/opentelemetry_semantic_conventions-0.66b1.tar.gz", hash = "sha256:497ca63bf383723411e8eaf60c8779e9877633c936bb641080adab59d0eb6ec8", size = 150250, upload-time = "2026-10-06T17:33:14.073Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b2/37/cc6a55e448deaa9b27377d087da8615a3416d8ad523d5960b78dbeadd02a/opentelemetry_semantic_conventions-0.61b0-py3-none-any.whl", hash = "sha256:fa530a96be229795f8cef353739b618148b0fe2b4b3f005e60e262926c4d38e2", size = 231621, upload-time = "2026-03-04T14:17:19.33Z" }, + { url = "https://files.pythonhosted.org/packages/bc/14/67f8aa798857f8cf686f515bf93d9bb877ce952ddc8efae0fa25b45ce0d6/opentelemetry_semantic_conventions-0.66b1-py3-none-any.whl", hash = "sha256:d4cddeb4315490b35213f55e2bdc9ac54bb1e4d318927475bed62b35545e581b", size = 206279, upload-time = "2026-10-06T17:32:56.103Z" }, ] [[package]] @@ -579,7 +597,7 @@ wheels = [ [[package]] name = "posthog" -version = "7.75.4" +version = "7.75.11" source = { directory = "../../" } dependencies = [ { name = "backoff" }, @@ -1039,12 +1057,3 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/46/78/10ad9781128ed2f99dbc474f43283b13fea8ba58723e98844367531c18e9/wrapt-1.17.3-cp314-cp314t-win_arm64.whl", hash = "sha256:f38e60678850c42461d4202739f9bf1e3a737c7ad283638251e79cc49effb6b6", size = 38471, upload-time = "2025-08-12T05:52:57.784Z" }, { url = "https://files.pythonhosted.org/packages/1f/f6/a933bd70f98e9cf3e08167fc5cd7aaaca49147e48411c0bd5ae701bb2194/wrapt-1.17.3-py3-none-any.whl", hash = "sha256:7171ae35d2c33d326ac19dd8facb1e82e5fd04ef8c6c0e394d7af55a55051c22", size = 23591, upload-time = "2025-08-12T05:53:20.674Z" }, ] - -[[package]] -name = "zipp" -version = "3.23.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166", size = 25547, upload-time = "2025-06-08T17:06:39.4Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276, upload-time = "2025-06-08T17:06:38.034Z" }, -] diff --git a/examples/example-ai-groq/uv.lock b/examples/example-ai-groq/uv.lock index 3a4b5205..53278b35 100644 --- a/examples/example-ai-groq/uv.lock +++ b/examples/example-ai-groq/uv.lock @@ -262,18 +262,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/57/b0/0e52c878c53f245edd3a11020f20979b3f490f245af532c7cae3027754b5/idna-3.19-py3-none-any.whl", hash = "sha256:815e7be7a7806d54abb586dc943addc79e8b2ee16915059658cbeff4b1b43bf4", size = 68550, upload-time = "2026-08-18T05:14:22.343Z" }, ] -[[package]] -name = "importlib-metadata" -version = "8.7.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "zipp" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/f3/49/3b30cad09e7771a4982d9975a8cbf64f00d4a1ececb53297f1d9a7be1b10/importlib_metadata-8.7.1.tar.gz", hash = "sha256:49fef1ae6440c182052f407c8d34a68f72efc36db9ca90dc0113398f2fdde8bb", size = 57107, upload-time = "2025-12-21T10:00:19.278Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fa/5e/f8e9a1d23b9c20a551a8a02ea3637b4642e22c2626e3a13a9a29cdea99eb/importlib_metadata-8.7.1-py3-none-any.whl", hash = "sha256:5a1f80bf1daa489495071efbb095d75a634cf28a8bc299581244063b53176151", size = 27865, upload-time = "2025-12-21T10:00:18.329Z" }, -] - [[package]] name = "jiter" version = "0.13.0" @@ -392,50 +380,80 @@ wheels = [ [[package]] name = "opentelemetry-api" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "importlib-metadata" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2c/1d/4049a9e8698361cc1a1aa03a6c59e4fa4c71e0c0f94a30f988a6876a2ae6/opentelemetry_api-1.40.0.tar.gz", hash = "sha256:159be641c0b04d11e9ecd576906462773eb97ae1b657730f0ecf64d32071569f", size = 70851, upload-time = "2026-03-04T14:17:21.555Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2e/02/6e0ae9cc61bd3169d401077b507b3ebc344745171e1051ab430be012dcd9/opentelemetry_api-1.45.1.tar.gz", hash = "sha256:aa38ed19bcc084ba42782a73255b3582283eced7ad6dddbd6695189e69adfb75", size = 72804, upload-time = "2026-10-06T17:32:58.133Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/41/f7dcf80b81ee8e71c1a2b59f14208bc723edbd89ed027a73b175abf6348e/opentelemetry_api-1.45.1-py3-none-any.whl", hash = "sha256:b31553efa588ae44bc306f863c785c5333a9ecc091248c6ee68b4b6c87fdedfb", size = 60256, upload-time = "2026-10-06T17:32:33.506Z" }, +] + +[[package]] +name = "opentelemetry-exporter-http-transport" +version = "0.66b1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/62/0c/e3ebdb4b507f66afcc905e6885a4946969bd75b45988492643356fbbdc63/opentelemetry_exporter_http_transport-0.66b1.tar.gz", hash = "sha256:443080203bf52586ce0b2ad901e8951c61833eab1aa539ae6f1f16fe9e8e7952", size = 11693, upload-time = "2026-10-06T17:32:59.65Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/69/6af86ff66492b481c6a4c05dcfd68beb47ed8ba046440a26a2aac76b95c7/opentelemetry_exporter_http_transport-0.66b1-py3-none-any.whl", hash = "sha256:2f95404bdee7f9d2d529c7de56c7bd86d014d774d8fbf137810e0167f8a492bf", size = 12155, upload-time = "2026-10-06T17:32:35.454Z" }, +] + +[package.optional-dependencies] +requests = [ + { name = "requests" }, +] + +[[package]] +name = "opentelemetry-exporter-otlp-common" +version = "0.66b1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-sdk" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cb/19/41de712173f43057e4532d42ece7d0c6d4210d353e5752433cb14987643f/opentelemetry_exporter_otlp_common-0.66b1.tar.gz", hash = "sha256:6b1403487a2185ac1feb45fd5546fdf8630ce71c36bcefaadf51e2130e9e23f9", size = 14325, upload-time = "2026-10-06T17:33:01.725Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5f/bf/93795954016c522008da367da292adceed71cca6ee1717e1d64c83089099/opentelemetry_api-1.40.0-py3-none-any.whl", hash = "sha256:82dd69331ae74b06f6a874704be0cfaa49a1650e1537d4a813b86ecef7d0ecf9", size = 68676, upload-time = "2026-03-04T14:17:01.24Z" }, + { url = "https://files.pythonhosted.org/packages/fc/39/8c23d67665c762aa51840fa06f86e902e8f6f1693bc8d7e3d98cd6e2f753/opentelemetry_exporter_otlp_common-0.66b1-py3-none-any.whl", hash = "sha256:00ff8592c3a7cb729ff3fdc7ffa12372c243bdf2163e80c180994d0c7bd83ee9", size = 12385, upload-time = "2026-10-06T17:32:38.177Z" }, ] [[package]] name = "opentelemetry-exporter-otlp-proto-common" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-proto" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/51/bc/1559d46557fe6eca0b46c88d4c2676285f1f3be2e8d06bb5d15fbffc814a/opentelemetry_exporter_otlp_proto_common-1.40.0.tar.gz", hash = "sha256:1cbee86a4064790b362a86601ee7934f368b81cd4cc2f2e163902a6e7818a0fa", size = 20416, upload-time = "2026-03-04T14:17:23.801Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c1/8e/65e85e5137991a3c493b11682151d198638a5bc1dd4b4c5f67e013c57d7c/opentelemetry_exporter_otlp_proto_common-1.45.1.tar.gz", hash = "sha256:2e4adcc3a67bcf57804fc49514f0ef64974ca7590aa3491da389852b4a0628f6", size = 18873, upload-time = "2026-10-06T17:33:04.471Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8b/ca/8f122055c97a932311a3f640273f084e738008933503d0c2563cd5d591fc/opentelemetry_exporter_otlp_proto_common-1.40.0-py3-none-any.whl", hash = "sha256:7081ff453835a82417bf38dccf122c827c3cbc94f2079b03bba02a3165f25149", size = 18369, upload-time = "2026-03-04T14:17:04.796Z" }, + { url = "https://files.pythonhosted.org/packages/84/aa/92f225d353904e7f70b8b3e3c1b02db0cf56f744c2e83c581dc372e78873/opentelemetry_exporter_otlp_proto_common-1.45.1-py3-none-any.whl", hash = "sha256:2f446183ae7047b036226f1d846c41a834b0e8755ad13b51a51dd38952eb466c", size = 15393, upload-time = "2026-10-06T17:32:41.911Z" }, ] [[package]] name = "opentelemetry-exporter-otlp-proto-http" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "googleapis-common-protos" }, { name = "opentelemetry-api" }, + { name = "opentelemetry-exporter-http-transport", extra = ["requests"] }, + { name = "opentelemetry-exporter-otlp-common" }, { name = "opentelemetry-exporter-otlp-proto-common" }, { name = "opentelemetry-proto" }, { name = "opentelemetry-sdk" }, { name = "requests" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2e/fa/73d50e2c15c56be4d000c98e24221d494674b0cc95524e2a8cb3856d95a4/opentelemetry_exporter_otlp_proto_http-1.40.0.tar.gz", hash = "sha256:db48f5e0f33217588bbc00274a31517ba830da576e59503507c839b38fa0869c", size = 17772, upload-time = "2026-03-04T14:17:25.324Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/17/26487707ea4caa97b17e6e4b5fa72133a53512ffa2f5cf7a49ef284b29cb/opentelemetry_exporter_otlp_proto_http-1.45.1.tar.gz", hash = "sha256:45c218405ce3fd879596924b1874bf9a8f6880206d61065c5a912c8e5c297fb7", size = 28839, upload-time = "2026-10-06T17:33:05.713Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/3a/8865d6754e61c9fb170cdd530a124a53769ee5f740236064816eb0ca7301/opentelemetry_exporter_otlp_proto_http-1.40.0-py3-none-any.whl", hash = "sha256:a8d1dab28f504c5d96577d6509f80a8150e44e8f45f82cdbe0e34c99ab040069", size = 19960, upload-time = "2026-03-04T14:17:07.153Z" }, + { url = "https://files.pythonhosted.org/packages/aa/1f/517eaa0187ba106a9da97160ce2add3a371812681dc440930b267f714e42/opentelemetry_exporter_otlp_proto_http-1.45.1-py3-none-any.whl", hash = "sha256:24a97cf3753c7fb52fad44a696e452ff371686339e2acf3309e2eda3d0230700", size = 22180, upload-time = "2026-10-06T17:32:43.946Z" }, ] [[package]] name = "opentelemetry-instrumentation" -version = "0.61b0" +version = "0.66b1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, @@ -443,9 +461,9 @@ dependencies = [ { name = "packaging" }, { name = "wrapt" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/da/37/6bf8e66bfcee5d3c6515b79cb2ee9ad05fe573c20f7ceb288d0e7eeec28c/opentelemetry_instrumentation-0.61b0.tar.gz", hash = "sha256:cb21b48db738c9de196eba6b805b4ff9de3b7f187e4bbf9a466fa170514f1fc7", size = 32606, upload-time = "2026-03-04T14:20:16.825Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a5/03/89e47ff8d52a4f83b343e6eb9ef1698ff45357216e5b6b2b21e0da5c5c7d/opentelemetry_instrumentation-0.66b1.tar.gz", hash = "sha256:e79a510f7d87c72d95e964ddb42193a0d9a75668c027d980eab032ea1322a5ce", size = 43308, upload-time = "2026-10-06T17:36:10.703Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d8/3e/f6f10f178b6316de67f0dfdbbb699a24fbe8917cf1743c1595fb9dcdd461/opentelemetry_instrumentation-0.61b0-py3-none-any.whl", hash = "sha256:92a93a280e69788e8f88391247cc530fd81f16f2b011979d4d6398f805cfbc63", size = 33448, upload-time = "2026-03-04T14:19:02.447Z" }, + { url = "https://files.pythonhosted.org/packages/da/b2/d1413681ff43e13ac9860df27e1226d3199ab0b97b352ceea41abcc660a5/opentelemetry_instrumentation-0.66b1-py3-none-any.whl", hash = "sha256:4c4aa14dc9a24a02325a9d4c42c4d0208dbb1374c2b1b8fe6c9392d59f3e1008", size = 36904, upload-time = "2026-10-06T17:35:11.663Z" }, ] [[package]] @@ -464,41 +482,41 @@ wheels = [ [[package]] name = "opentelemetry-proto" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "protobuf" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4c/77/dd38991db037fdfce45849491cb61de5ab000f49824a00230afb112a4392/opentelemetry_proto-1.40.0.tar.gz", hash = "sha256:03f639ca129ba513f5819810f5b1f42bcb371391405d99c168fe6937c62febcd", size = 45667, upload-time = "2026-03-04T14:17:31.194Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4b/7f/15f014fb195da6c2dbb6c71399b8e76824878718e94de6454038488eed28/opentelemetry_proto-1.45.1.tar.gz", hash = "sha256:79e0fb95e4616691a469439238aa9224d75779b3e108e895d1aa125ab29ca77c", size = 46488, upload-time = "2026-10-06T17:33:11.49Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b9/b2/189b2577dde745b15625b3214302605b1353436219d42b7912e77fa8dc24/opentelemetry_proto-1.40.0-py3-none-any.whl", hash = "sha256:266c4385d88923a23d63e353e9761af0f47a6ed0d486979777fe4de59dc9b25f", size = 72073, upload-time = "2026-03-04T14:17:16.673Z" }, + { url = "https://files.pythonhosted.org/packages/ab/9a/42ec8180a769516ae757e893b69736826efceac7332553915b4528a91c6d/opentelemetry_proto-1.45.1-py3-none-any.whl", hash = "sha256:f38e2a8413053c180cd3d2637fbb279673ec2f6a6e09c995aafa2f452c52b46e", size = 72488, upload-time = "2026-10-06T17:32:53.057Z" }, ] [[package]] name = "opentelemetry-sdk" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, { name = "opentelemetry-semantic-conventions" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/58/fd/3c3125b20ba18ce2155ba9ea74acb0ae5d25f8cd39cfd37455601b7955cc/opentelemetry_sdk-1.40.0.tar.gz", hash = "sha256:18e9f5ec20d859d268c7cb3c5198c8d105d073714db3de50b593b8c1345a48f2", size = 184252, upload-time = "2026-03-04T14:17:31.87Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/79/7392e21a1c8f0c61d90b223e31c7e48cb9d452e91a6b820ad24cca5f23c4/opentelemetry_sdk-1.45.1.tar.gz", hash = "sha256:63d24a6ca645019a631e6a51999c73e93adcac1196ca640b8ae78a7cc4762bf3", size = 218324, upload-time = "2026-10-06T17:33:13.26Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/c5/6a852903d8bfac758c6dc6e9a68b015d3c33f2f1be5e9591e0f4b69c7e0a/opentelemetry_sdk-1.40.0-py3-none-any.whl", hash = "sha256:787d2154a71f4b3d81f20524a8ce061b7db667d24e46753f32a7bc48f1c1f3f1", size = 141951, upload-time = "2026-03-04T14:17:17.961Z" }, + { url = "https://files.pythonhosted.org/packages/95/3c/87c42b4bd6dd297536f04cd9383d212ac557ecd49f2cbdcd46da1c9ef5c8/opentelemetry_sdk-1.45.1-py3-none-any.whl", hash = "sha256:c604c11dc429810812348989115fa44bd558772a3d7442afc43d024f2c250ca4", size = 140063, upload-time = "2026-10-06T17:32:55.04Z" }, ] [[package]] name = "opentelemetry-semantic-conventions" -version = "0.61b0" +version = "0.66b1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6d/c0/4ae7973f3c2cfd2b6e321f1675626f0dab0a97027cc7a297474c9c8f3d04/opentelemetry_semantic_conventions-0.61b0.tar.gz", hash = "sha256:072f65473c5d7c6dc0355b27d6c9d1a679d63b6d4b4b16a9773062cb7e31192a", size = 145755, upload-time = "2026-03-04T14:17:32.664Z" } +sdist = { url = "https://files.pythonhosted.org/packages/46/e4/dbbfb2a010c4db2224a5114638acede6fe563d33cc20fb1752cebcbe6298/opentelemetry_semantic_conventions-0.66b1.tar.gz", hash = "sha256:497ca63bf383723411e8eaf60c8779e9877633c936bb641080adab59d0eb6ec8", size = 150250, upload-time = "2026-10-06T17:33:14.073Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b2/37/cc6a55e448deaa9b27377d087da8615a3416d8ad523d5960b78dbeadd02a/opentelemetry_semantic_conventions-0.61b0-py3-none-any.whl", hash = "sha256:fa530a96be229795f8cef353739b618148b0fe2b4b3f005e60e262926c4d38e2", size = 231621, upload-time = "2026-03-04T14:17:19.33Z" }, + { url = "https://files.pythonhosted.org/packages/bc/14/67f8aa798857f8cf686f515bf93d9bb877ce952ddc8efae0fa25b45ce0d6/opentelemetry_semantic_conventions-0.66b1-py3-none-any.whl", hash = "sha256:d4cddeb4315490b35213f55e2bdc9ac54bb1e4d318927475bed62b35545e581b", size = 206279, upload-time = "2026-10-06T17:32:56.103Z" }, ] [[package]] @@ -512,7 +530,7 @@ wheels = [ [[package]] name = "posthog" -version = "7.75.4" +version = "7.75.11" source = { directory = "../../" } dependencies = [ { name = "backoff" }, @@ -877,12 +895,3 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/46/78/10ad9781128ed2f99dbc474f43283b13fea8ba58723e98844367531c18e9/wrapt-1.17.3-cp314-cp314t-win_arm64.whl", hash = "sha256:f38e60678850c42461d4202739f9bf1e3a737c7ad283638251e79cc49effb6b6", size = 38471, upload-time = "2025-08-12T05:52:57.784Z" }, { url = "https://files.pythonhosted.org/packages/1f/f6/a933bd70f98e9cf3e08167fc5cd7aaaca49147e48411c0bd5ae701bb2194/wrapt-1.17.3-py3-none-any.whl", hash = "sha256:7171ae35d2c33d326ac19dd8facb1e82e5fd04ef8c6c0e394d7af55a55051c22", size = 23591, upload-time = "2025-08-12T05:53:20.674Z" }, ] - -[[package]] -name = "zipp" -version = "3.23.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166", size = 25547, upload-time = "2025-06-08T17:06:39.4Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276, upload-time = "2025-06-08T17:06:38.034Z" }, -] diff --git a/examples/example-ai-helicone/uv.lock b/examples/example-ai-helicone/uv.lock index 28b21350..803a8a6d 100644 --- a/examples/example-ai-helicone/uv.lock +++ b/examples/example-ai-helicone/uv.lock @@ -262,18 +262,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/57/b0/0e52c878c53f245edd3a11020f20979b3f490f245af532c7cae3027754b5/idna-3.19-py3-none-any.whl", hash = "sha256:815e7be7a7806d54abb586dc943addc79e8b2ee16915059658cbeff4b1b43bf4", size = 68550, upload-time = "2026-08-18T05:14:22.343Z" }, ] -[[package]] -name = "importlib-metadata" -version = "8.7.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "zipp" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/f3/49/3b30cad09e7771a4982d9975a8cbf64f00d4a1ececb53297f1d9a7be1b10/importlib_metadata-8.7.1.tar.gz", hash = "sha256:49fef1ae6440c182052f407c8d34a68f72efc36db9ca90dc0113398f2fdde8bb", size = 57107, upload-time = "2025-12-21T10:00:19.278Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fa/5e/f8e9a1d23b9c20a551a8a02ea3637b4642e22c2626e3a13a9a29cdea99eb/importlib_metadata-8.7.1-py3-none-any.whl", hash = "sha256:5a1f80bf1daa489495071efbb095d75a634cf28a8bc299581244063b53176151", size = 27865, upload-time = "2025-12-21T10:00:18.329Z" }, -] - [[package]] name = "jiter" version = "0.13.0" @@ -392,50 +380,80 @@ wheels = [ [[package]] name = "opentelemetry-api" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "importlib-metadata" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2c/1d/4049a9e8698361cc1a1aa03a6c59e4fa4c71e0c0f94a30f988a6876a2ae6/opentelemetry_api-1.40.0.tar.gz", hash = "sha256:159be641c0b04d11e9ecd576906462773eb97ae1b657730f0ecf64d32071569f", size = 70851, upload-time = "2026-03-04T14:17:21.555Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2e/02/6e0ae9cc61bd3169d401077b507b3ebc344745171e1051ab430be012dcd9/opentelemetry_api-1.45.1.tar.gz", hash = "sha256:aa38ed19bcc084ba42782a73255b3582283eced7ad6dddbd6695189e69adfb75", size = 72804, upload-time = "2026-10-06T17:32:58.133Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/41/f7dcf80b81ee8e71c1a2b59f14208bc723edbd89ed027a73b175abf6348e/opentelemetry_api-1.45.1-py3-none-any.whl", hash = "sha256:b31553efa588ae44bc306f863c785c5333a9ecc091248c6ee68b4b6c87fdedfb", size = 60256, upload-time = "2026-10-06T17:32:33.506Z" }, +] + +[[package]] +name = "opentelemetry-exporter-http-transport" +version = "0.66b1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/62/0c/e3ebdb4b507f66afcc905e6885a4946969bd75b45988492643356fbbdc63/opentelemetry_exporter_http_transport-0.66b1.tar.gz", hash = "sha256:443080203bf52586ce0b2ad901e8951c61833eab1aa539ae6f1f16fe9e8e7952", size = 11693, upload-time = "2026-10-06T17:32:59.65Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/69/6af86ff66492b481c6a4c05dcfd68beb47ed8ba046440a26a2aac76b95c7/opentelemetry_exporter_http_transport-0.66b1-py3-none-any.whl", hash = "sha256:2f95404bdee7f9d2d529c7de56c7bd86d014d774d8fbf137810e0167f8a492bf", size = 12155, upload-time = "2026-10-06T17:32:35.454Z" }, +] + +[package.optional-dependencies] +requests = [ + { name = "requests" }, +] + +[[package]] +name = "opentelemetry-exporter-otlp-common" +version = "0.66b1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-sdk" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cb/19/41de712173f43057e4532d42ece7d0c6d4210d353e5752433cb14987643f/opentelemetry_exporter_otlp_common-0.66b1.tar.gz", hash = "sha256:6b1403487a2185ac1feb45fd5546fdf8630ce71c36bcefaadf51e2130e9e23f9", size = 14325, upload-time = "2026-10-06T17:33:01.725Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5f/bf/93795954016c522008da367da292adceed71cca6ee1717e1d64c83089099/opentelemetry_api-1.40.0-py3-none-any.whl", hash = "sha256:82dd69331ae74b06f6a874704be0cfaa49a1650e1537d4a813b86ecef7d0ecf9", size = 68676, upload-time = "2026-03-04T14:17:01.24Z" }, + { url = "https://files.pythonhosted.org/packages/fc/39/8c23d67665c762aa51840fa06f86e902e8f6f1693bc8d7e3d98cd6e2f753/opentelemetry_exporter_otlp_common-0.66b1-py3-none-any.whl", hash = "sha256:00ff8592c3a7cb729ff3fdc7ffa12372c243bdf2163e80c180994d0c7bd83ee9", size = 12385, upload-time = "2026-10-06T17:32:38.177Z" }, ] [[package]] name = "opentelemetry-exporter-otlp-proto-common" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-proto" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/51/bc/1559d46557fe6eca0b46c88d4c2676285f1f3be2e8d06bb5d15fbffc814a/opentelemetry_exporter_otlp_proto_common-1.40.0.tar.gz", hash = "sha256:1cbee86a4064790b362a86601ee7934f368b81cd4cc2f2e163902a6e7818a0fa", size = 20416, upload-time = "2026-03-04T14:17:23.801Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c1/8e/65e85e5137991a3c493b11682151d198638a5bc1dd4b4c5f67e013c57d7c/opentelemetry_exporter_otlp_proto_common-1.45.1.tar.gz", hash = "sha256:2e4adcc3a67bcf57804fc49514f0ef64974ca7590aa3491da389852b4a0628f6", size = 18873, upload-time = "2026-10-06T17:33:04.471Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8b/ca/8f122055c97a932311a3f640273f084e738008933503d0c2563cd5d591fc/opentelemetry_exporter_otlp_proto_common-1.40.0-py3-none-any.whl", hash = "sha256:7081ff453835a82417bf38dccf122c827c3cbc94f2079b03bba02a3165f25149", size = 18369, upload-time = "2026-03-04T14:17:04.796Z" }, + { url = "https://files.pythonhosted.org/packages/84/aa/92f225d353904e7f70b8b3e3c1b02db0cf56f744c2e83c581dc372e78873/opentelemetry_exporter_otlp_proto_common-1.45.1-py3-none-any.whl", hash = "sha256:2f446183ae7047b036226f1d846c41a834b0e8755ad13b51a51dd38952eb466c", size = 15393, upload-time = "2026-10-06T17:32:41.911Z" }, ] [[package]] name = "opentelemetry-exporter-otlp-proto-http" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "googleapis-common-protos" }, { name = "opentelemetry-api" }, + { name = "opentelemetry-exporter-http-transport", extra = ["requests"] }, + { name = "opentelemetry-exporter-otlp-common" }, { name = "opentelemetry-exporter-otlp-proto-common" }, { name = "opentelemetry-proto" }, { name = "opentelemetry-sdk" }, { name = "requests" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2e/fa/73d50e2c15c56be4d000c98e24221d494674b0cc95524e2a8cb3856d95a4/opentelemetry_exporter_otlp_proto_http-1.40.0.tar.gz", hash = "sha256:db48f5e0f33217588bbc00274a31517ba830da576e59503507c839b38fa0869c", size = 17772, upload-time = "2026-03-04T14:17:25.324Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/17/26487707ea4caa97b17e6e4b5fa72133a53512ffa2f5cf7a49ef284b29cb/opentelemetry_exporter_otlp_proto_http-1.45.1.tar.gz", hash = "sha256:45c218405ce3fd879596924b1874bf9a8f6880206d61065c5a912c8e5c297fb7", size = 28839, upload-time = "2026-10-06T17:33:05.713Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/3a/8865d6754e61c9fb170cdd530a124a53769ee5f740236064816eb0ca7301/opentelemetry_exporter_otlp_proto_http-1.40.0-py3-none-any.whl", hash = "sha256:a8d1dab28f504c5d96577d6509f80a8150e44e8f45f82cdbe0e34c99ab040069", size = 19960, upload-time = "2026-03-04T14:17:07.153Z" }, + { url = "https://files.pythonhosted.org/packages/aa/1f/517eaa0187ba106a9da97160ce2add3a371812681dc440930b267f714e42/opentelemetry_exporter_otlp_proto_http-1.45.1-py3-none-any.whl", hash = "sha256:24a97cf3753c7fb52fad44a696e452ff371686339e2acf3309e2eda3d0230700", size = 22180, upload-time = "2026-10-06T17:32:43.946Z" }, ] [[package]] name = "opentelemetry-instrumentation" -version = "0.61b0" +version = "0.66b1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, @@ -443,9 +461,9 @@ dependencies = [ { name = "packaging" }, { name = "wrapt" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/da/37/6bf8e66bfcee5d3c6515b79cb2ee9ad05fe573c20f7ceb288d0e7eeec28c/opentelemetry_instrumentation-0.61b0.tar.gz", hash = "sha256:cb21b48db738c9de196eba6b805b4ff9de3b7f187e4bbf9a466fa170514f1fc7", size = 32606, upload-time = "2026-03-04T14:20:16.825Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a5/03/89e47ff8d52a4f83b343e6eb9ef1698ff45357216e5b6b2b21e0da5c5c7d/opentelemetry_instrumentation-0.66b1.tar.gz", hash = "sha256:e79a510f7d87c72d95e964ddb42193a0d9a75668c027d980eab032ea1322a5ce", size = 43308, upload-time = "2026-10-06T17:36:10.703Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d8/3e/f6f10f178b6316de67f0dfdbbb699a24fbe8917cf1743c1595fb9dcdd461/opentelemetry_instrumentation-0.61b0-py3-none-any.whl", hash = "sha256:92a93a280e69788e8f88391247cc530fd81f16f2b011979d4d6398f805cfbc63", size = 33448, upload-time = "2026-03-04T14:19:02.447Z" }, + { url = "https://files.pythonhosted.org/packages/da/b2/d1413681ff43e13ac9860df27e1226d3199ab0b97b352ceea41abcc660a5/opentelemetry_instrumentation-0.66b1-py3-none-any.whl", hash = "sha256:4c4aa14dc9a24a02325a9d4c42c4d0208dbb1374c2b1b8fe6c9392d59f3e1008", size = 36904, upload-time = "2026-10-06T17:35:11.663Z" }, ] [[package]] @@ -464,41 +482,41 @@ wheels = [ [[package]] name = "opentelemetry-proto" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "protobuf" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4c/77/dd38991db037fdfce45849491cb61de5ab000f49824a00230afb112a4392/opentelemetry_proto-1.40.0.tar.gz", hash = "sha256:03f639ca129ba513f5819810f5b1f42bcb371391405d99c168fe6937c62febcd", size = 45667, upload-time = "2026-03-04T14:17:31.194Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4b/7f/15f014fb195da6c2dbb6c71399b8e76824878718e94de6454038488eed28/opentelemetry_proto-1.45.1.tar.gz", hash = "sha256:79e0fb95e4616691a469439238aa9224d75779b3e108e895d1aa125ab29ca77c", size = 46488, upload-time = "2026-10-06T17:33:11.49Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b9/b2/189b2577dde745b15625b3214302605b1353436219d42b7912e77fa8dc24/opentelemetry_proto-1.40.0-py3-none-any.whl", hash = "sha256:266c4385d88923a23d63e353e9761af0f47a6ed0d486979777fe4de59dc9b25f", size = 72073, upload-time = "2026-03-04T14:17:16.673Z" }, + { url = "https://files.pythonhosted.org/packages/ab/9a/42ec8180a769516ae757e893b69736826efceac7332553915b4528a91c6d/opentelemetry_proto-1.45.1-py3-none-any.whl", hash = "sha256:f38e2a8413053c180cd3d2637fbb279673ec2f6a6e09c995aafa2f452c52b46e", size = 72488, upload-time = "2026-10-06T17:32:53.057Z" }, ] [[package]] name = "opentelemetry-sdk" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, { name = "opentelemetry-semantic-conventions" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/58/fd/3c3125b20ba18ce2155ba9ea74acb0ae5d25f8cd39cfd37455601b7955cc/opentelemetry_sdk-1.40.0.tar.gz", hash = "sha256:18e9f5ec20d859d268c7cb3c5198c8d105d073714db3de50b593b8c1345a48f2", size = 184252, upload-time = "2026-03-04T14:17:31.87Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/79/7392e21a1c8f0c61d90b223e31c7e48cb9d452e91a6b820ad24cca5f23c4/opentelemetry_sdk-1.45.1.tar.gz", hash = "sha256:63d24a6ca645019a631e6a51999c73e93adcac1196ca640b8ae78a7cc4762bf3", size = 218324, upload-time = "2026-10-06T17:33:13.26Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/c5/6a852903d8bfac758c6dc6e9a68b015d3c33f2f1be5e9591e0f4b69c7e0a/opentelemetry_sdk-1.40.0-py3-none-any.whl", hash = "sha256:787d2154a71f4b3d81f20524a8ce061b7db667d24e46753f32a7bc48f1c1f3f1", size = 141951, upload-time = "2026-03-04T14:17:17.961Z" }, + { url = "https://files.pythonhosted.org/packages/95/3c/87c42b4bd6dd297536f04cd9383d212ac557ecd49f2cbdcd46da1c9ef5c8/opentelemetry_sdk-1.45.1-py3-none-any.whl", hash = "sha256:c604c11dc429810812348989115fa44bd558772a3d7442afc43d024f2c250ca4", size = 140063, upload-time = "2026-10-06T17:32:55.04Z" }, ] [[package]] name = "opentelemetry-semantic-conventions" -version = "0.61b0" +version = "0.66b1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6d/c0/4ae7973f3c2cfd2b6e321f1675626f0dab0a97027cc7a297474c9c8f3d04/opentelemetry_semantic_conventions-0.61b0.tar.gz", hash = "sha256:072f65473c5d7c6dc0355b27d6c9d1a679d63b6d4b4b16a9773062cb7e31192a", size = 145755, upload-time = "2026-03-04T14:17:32.664Z" } +sdist = { url = "https://files.pythonhosted.org/packages/46/e4/dbbfb2a010c4db2224a5114638acede6fe563d33cc20fb1752cebcbe6298/opentelemetry_semantic_conventions-0.66b1.tar.gz", hash = "sha256:497ca63bf383723411e8eaf60c8779e9877633c936bb641080adab59d0eb6ec8", size = 150250, upload-time = "2026-10-06T17:33:14.073Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b2/37/cc6a55e448deaa9b27377d087da8615a3416d8ad523d5960b78dbeadd02a/opentelemetry_semantic_conventions-0.61b0-py3-none-any.whl", hash = "sha256:fa530a96be229795f8cef353739b618148b0fe2b4b3f005e60e262926c4d38e2", size = 231621, upload-time = "2026-03-04T14:17:19.33Z" }, + { url = "https://files.pythonhosted.org/packages/bc/14/67f8aa798857f8cf686f515bf93d9bb877ce952ddc8efae0fa25b45ce0d6/opentelemetry_semantic_conventions-0.66b1-py3-none-any.whl", hash = "sha256:d4cddeb4315490b35213f55e2bdc9ac54bb1e4d318927475bed62b35545e581b", size = 206279, upload-time = "2026-10-06T17:32:56.103Z" }, ] [[package]] @@ -512,7 +530,7 @@ wheels = [ [[package]] name = "posthog" -version = "7.75.4" +version = "7.75.11" source = { directory = "../../" } dependencies = [ { name = "backoff" }, @@ -877,12 +895,3 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/46/78/10ad9781128ed2f99dbc474f43283b13fea8ba58723e98844367531c18e9/wrapt-1.17.3-cp314-cp314t-win_arm64.whl", hash = "sha256:f38e60678850c42461d4202739f9bf1e3a737c7ad283638251e79cc49effb6b6", size = 38471, upload-time = "2025-08-12T05:52:57.784Z" }, { url = "https://files.pythonhosted.org/packages/1f/f6/a933bd70f98e9cf3e08167fc5cd7aaaca49147e48411c0bd5ae701bb2194/wrapt-1.17.3-py3-none-any.whl", hash = "sha256:7171ae35d2c33d326ac19dd8facb1e82e5fd04ef8c6c0e394d7af55a55051c22", size = 23591, upload-time = "2025-08-12T05:53:20.674Z" }, ] - -[[package]] -name = "zipp" -version = "3.23.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166", size = 25547, upload-time = "2025-06-08T17:06:39.4Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276, upload-time = "2025-06-08T17:06:38.034Z" }, -] diff --git a/examples/example-ai-hugging-face/uv.lock b/examples/example-ai-hugging-face/uv.lock index c911563a..b853347c 100644 --- a/examples/example-ai-hugging-face/uv.lock +++ b/examples/example-ai-hugging-face/uv.lock @@ -262,18 +262,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/57/b0/0e52c878c53f245edd3a11020f20979b3f490f245af532c7cae3027754b5/idna-3.19-py3-none-any.whl", hash = "sha256:815e7be7a7806d54abb586dc943addc79e8b2ee16915059658cbeff4b1b43bf4", size = 68550, upload-time = "2026-08-18T05:14:22.343Z" }, ] -[[package]] -name = "importlib-metadata" -version = "8.7.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "zipp" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/f3/49/3b30cad09e7771a4982d9975a8cbf64f00d4a1ececb53297f1d9a7be1b10/importlib_metadata-8.7.1.tar.gz", hash = "sha256:49fef1ae6440c182052f407c8d34a68f72efc36db9ca90dc0113398f2fdde8bb", size = 57107, upload-time = "2025-12-21T10:00:19.278Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fa/5e/f8e9a1d23b9c20a551a8a02ea3637b4642e22c2626e3a13a9a29cdea99eb/importlib_metadata-8.7.1-py3-none-any.whl", hash = "sha256:5a1f80bf1daa489495071efbb095d75a634cf28a8bc299581244063b53176151", size = 27865, upload-time = "2025-12-21T10:00:18.329Z" }, -] - [[package]] name = "jiter" version = "0.13.0" @@ -392,50 +380,80 @@ wheels = [ [[package]] name = "opentelemetry-api" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "importlib-metadata" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2c/1d/4049a9e8698361cc1a1aa03a6c59e4fa4c71e0c0f94a30f988a6876a2ae6/opentelemetry_api-1.40.0.tar.gz", hash = "sha256:159be641c0b04d11e9ecd576906462773eb97ae1b657730f0ecf64d32071569f", size = 70851, upload-time = "2026-03-04T14:17:21.555Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2e/02/6e0ae9cc61bd3169d401077b507b3ebc344745171e1051ab430be012dcd9/opentelemetry_api-1.45.1.tar.gz", hash = "sha256:aa38ed19bcc084ba42782a73255b3582283eced7ad6dddbd6695189e69adfb75", size = 72804, upload-time = "2026-10-06T17:32:58.133Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/41/f7dcf80b81ee8e71c1a2b59f14208bc723edbd89ed027a73b175abf6348e/opentelemetry_api-1.45.1-py3-none-any.whl", hash = "sha256:b31553efa588ae44bc306f863c785c5333a9ecc091248c6ee68b4b6c87fdedfb", size = 60256, upload-time = "2026-10-06T17:32:33.506Z" }, +] + +[[package]] +name = "opentelemetry-exporter-http-transport" +version = "0.66b1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/62/0c/e3ebdb4b507f66afcc905e6885a4946969bd75b45988492643356fbbdc63/opentelemetry_exporter_http_transport-0.66b1.tar.gz", hash = "sha256:443080203bf52586ce0b2ad901e8951c61833eab1aa539ae6f1f16fe9e8e7952", size = 11693, upload-time = "2026-10-06T17:32:59.65Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/69/6af86ff66492b481c6a4c05dcfd68beb47ed8ba046440a26a2aac76b95c7/opentelemetry_exporter_http_transport-0.66b1-py3-none-any.whl", hash = "sha256:2f95404bdee7f9d2d529c7de56c7bd86d014d774d8fbf137810e0167f8a492bf", size = 12155, upload-time = "2026-10-06T17:32:35.454Z" }, +] + +[package.optional-dependencies] +requests = [ + { name = "requests" }, +] + +[[package]] +name = "opentelemetry-exporter-otlp-common" +version = "0.66b1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-sdk" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cb/19/41de712173f43057e4532d42ece7d0c6d4210d353e5752433cb14987643f/opentelemetry_exporter_otlp_common-0.66b1.tar.gz", hash = "sha256:6b1403487a2185ac1feb45fd5546fdf8630ce71c36bcefaadf51e2130e9e23f9", size = 14325, upload-time = "2026-10-06T17:33:01.725Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5f/bf/93795954016c522008da367da292adceed71cca6ee1717e1d64c83089099/opentelemetry_api-1.40.0-py3-none-any.whl", hash = "sha256:82dd69331ae74b06f6a874704be0cfaa49a1650e1537d4a813b86ecef7d0ecf9", size = 68676, upload-time = "2026-03-04T14:17:01.24Z" }, + { url = "https://files.pythonhosted.org/packages/fc/39/8c23d67665c762aa51840fa06f86e902e8f6f1693bc8d7e3d98cd6e2f753/opentelemetry_exporter_otlp_common-0.66b1-py3-none-any.whl", hash = "sha256:00ff8592c3a7cb729ff3fdc7ffa12372c243bdf2163e80c180994d0c7bd83ee9", size = 12385, upload-time = "2026-10-06T17:32:38.177Z" }, ] [[package]] name = "opentelemetry-exporter-otlp-proto-common" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-proto" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/51/bc/1559d46557fe6eca0b46c88d4c2676285f1f3be2e8d06bb5d15fbffc814a/opentelemetry_exporter_otlp_proto_common-1.40.0.tar.gz", hash = "sha256:1cbee86a4064790b362a86601ee7934f368b81cd4cc2f2e163902a6e7818a0fa", size = 20416, upload-time = "2026-03-04T14:17:23.801Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c1/8e/65e85e5137991a3c493b11682151d198638a5bc1dd4b4c5f67e013c57d7c/opentelemetry_exporter_otlp_proto_common-1.45.1.tar.gz", hash = "sha256:2e4adcc3a67bcf57804fc49514f0ef64974ca7590aa3491da389852b4a0628f6", size = 18873, upload-time = "2026-10-06T17:33:04.471Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8b/ca/8f122055c97a932311a3f640273f084e738008933503d0c2563cd5d591fc/opentelemetry_exporter_otlp_proto_common-1.40.0-py3-none-any.whl", hash = "sha256:7081ff453835a82417bf38dccf122c827c3cbc94f2079b03bba02a3165f25149", size = 18369, upload-time = "2026-03-04T14:17:04.796Z" }, + { url = "https://files.pythonhosted.org/packages/84/aa/92f225d353904e7f70b8b3e3c1b02db0cf56f744c2e83c581dc372e78873/opentelemetry_exporter_otlp_proto_common-1.45.1-py3-none-any.whl", hash = "sha256:2f446183ae7047b036226f1d846c41a834b0e8755ad13b51a51dd38952eb466c", size = 15393, upload-time = "2026-10-06T17:32:41.911Z" }, ] [[package]] name = "opentelemetry-exporter-otlp-proto-http" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "googleapis-common-protos" }, { name = "opentelemetry-api" }, + { name = "opentelemetry-exporter-http-transport", extra = ["requests"] }, + { name = "opentelemetry-exporter-otlp-common" }, { name = "opentelemetry-exporter-otlp-proto-common" }, { name = "opentelemetry-proto" }, { name = "opentelemetry-sdk" }, { name = "requests" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2e/fa/73d50e2c15c56be4d000c98e24221d494674b0cc95524e2a8cb3856d95a4/opentelemetry_exporter_otlp_proto_http-1.40.0.tar.gz", hash = "sha256:db48f5e0f33217588bbc00274a31517ba830da576e59503507c839b38fa0869c", size = 17772, upload-time = "2026-03-04T14:17:25.324Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/17/26487707ea4caa97b17e6e4b5fa72133a53512ffa2f5cf7a49ef284b29cb/opentelemetry_exporter_otlp_proto_http-1.45.1.tar.gz", hash = "sha256:45c218405ce3fd879596924b1874bf9a8f6880206d61065c5a912c8e5c297fb7", size = 28839, upload-time = "2026-10-06T17:33:05.713Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/3a/8865d6754e61c9fb170cdd530a124a53769ee5f740236064816eb0ca7301/opentelemetry_exporter_otlp_proto_http-1.40.0-py3-none-any.whl", hash = "sha256:a8d1dab28f504c5d96577d6509f80a8150e44e8f45f82cdbe0e34c99ab040069", size = 19960, upload-time = "2026-03-04T14:17:07.153Z" }, + { url = "https://files.pythonhosted.org/packages/aa/1f/517eaa0187ba106a9da97160ce2add3a371812681dc440930b267f714e42/opentelemetry_exporter_otlp_proto_http-1.45.1-py3-none-any.whl", hash = "sha256:24a97cf3753c7fb52fad44a696e452ff371686339e2acf3309e2eda3d0230700", size = 22180, upload-time = "2026-10-06T17:32:43.946Z" }, ] [[package]] name = "opentelemetry-instrumentation" -version = "0.61b0" +version = "0.66b1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, @@ -443,9 +461,9 @@ dependencies = [ { name = "packaging" }, { name = "wrapt" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/da/37/6bf8e66bfcee5d3c6515b79cb2ee9ad05fe573c20f7ceb288d0e7eeec28c/opentelemetry_instrumentation-0.61b0.tar.gz", hash = "sha256:cb21b48db738c9de196eba6b805b4ff9de3b7f187e4bbf9a466fa170514f1fc7", size = 32606, upload-time = "2026-03-04T14:20:16.825Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a5/03/89e47ff8d52a4f83b343e6eb9ef1698ff45357216e5b6b2b21e0da5c5c7d/opentelemetry_instrumentation-0.66b1.tar.gz", hash = "sha256:e79a510f7d87c72d95e964ddb42193a0d9a75668c027d980eab032ea1322a5ce", size = 43308, upload-time = "2026-10-06T17:36:10.703Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d8/3e/f6f10f178b6316de67f0dfdbbb699a24fbe8917cf1743c1595fb9dcdd461/opentelemetry_instrumentation-0.61b0-py3-none-any.whl", hash = "sha256:92a93a280e69788e8f88391247cc530fd81f16f2b011979d4d6398f805cfbc63", size = 33448, upload-time = "2026-03-04T14:19:02.447Z" }, + { url = "https://files.pythonhosted.org/packages/da/b2/d1413681ff43e13ac9860df27e1226d3199ab0b97b352ceea41abcc660a5/opentelemetry_instrumentation-0.66b1-py3-none-any.whl", hash = "sha256:4c4aa14dc9a24a02325a9d4c42c4d0208dbb1374c2b1b8fe6c9392d59f3e1008", size = 36904, upload-time = "2026-10-06T17:35:11.663Z" }, ] [[package]] @@ -464,41 +482,41 @@ wheels = [ [[package]] name = "opentelemetry-proto" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "protobuf" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4c/77/dd38991db037fdfce45849491cb61de5ab000f49824a00230afb112a4392/opentelemetry_proto-1.40.0.tar.gz", hash = "sha256:03f639ca129ba513f5819810f5b1f42bcb371391405d99c168fe6937c62febcd", size = 45667, upload-time = "2026-03-04T14:17:31.194Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4b/7f/15f014fb195da6c2dbb6c71399b8e76824878718e94de6454038488eed28/opentelemetry_proto-1.45.1.tar.gz", hash = "sha256:79e0fb95e4616691a469439238aa9224d75779b3e108e895d1aa125ab29ca77c", size = 46488, upload-time = "2026-10-06T17:33:11.49Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b9/b2/189b2577dde745b15625b3214302605b1353436219d42b7912e77fa8dc24/opentelemetry_proto-1.40.0-py3-none-any.whl", hash = "sha256:266c4385d88923a23d63e353e9761af0f47a6ed0d486979777fe4de59dc9b25f", size = 72073, upload-time = "2026-03-04T14:17:16.673Z" }, + { url = "https://files.pythonhosted.org/packages/ab/9a/42ec8180a769516ae757e893b69736826efceac7332553915b4528a91c6d/opentelemetry_proto-1.45.1-py3-none-any.whl", hash = "sha256:f38e2a8413053c180cd3d2637fbb279673ec2f6a6e09c995aafa2f452c52b46e", size = 72488, upload-time = "2026-10-06T17:32:53.057Z" }, ] [[package]] name = "opentelemetry-sdk" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, { name = "opentelemetry-semantic-conventions" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/58/fd/3c3125b20ba18ce2155ba9ea74acb0ae5d25f8cd39cfd37455601b7955cc/opentelemetry_sdk-1.40.0.tar.gz", hash = "sha256:18e9f5ec20d859d268c7cb3c5198c8d105d073714db3de50b593b8c1345a48f2", size = 184252, upload-time = "2026-03-04T14:17:31.87Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/79/7392e21a1c8f0c61d90b223e31c7e48cb9d452e91a6b820ad24cca5f23c4/opentelemetry_sdk-1.45.1.tar.gz", hash = "sha256:63d24a6ca645019a631e6a51999c73e93adcac1196ca640b8ae78a7cc4762bf3", size = 218324, upload-time = "2026-10-06T17:33:13.26Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/c5/6a852903d8bfac758c6dc6e9a68b015d3c33f2f1be5e9591e0f4b69c7e0a/opentelemetry_sdk-1.40.0-py3-none-any.whl", hash = "sha256:787d2154a71f4b3d81f20524a8ce061b7db667d24e46753f32a7bc48f1c1f3f1", size = 141951, upload-time = "2026-03-04T14:17:17.961Z" }, + { url = "https://files.pythonhosted.org/packages/95/3c/87c42b4bd6dd297536f04cd9383d212ac557ecd49f2cbdcd46da1c9ef5c8/opentelemetry_sdk-1.45.1-py3-none-any.whl", hash = "sha256:c604c11dc429810812348989115fa44bd558772a3d7442afc43d024f2c250ca4", size = 140063, upload-time = "2026-10-06T17:32:55.04Z" }, ] [[package]] name = "opentelemetry-semantic-conventions" -version = "0.61b0" +version = "0.66b1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6d/c0/4ae7973f3c2cfd2b6e321f1675626f0dab0a97027cc7a297474c9c8f3d04/opentelemetry_semantic_conventions-0.61b0.tar.gz", hash = "sha256:072f65473c5d7c6dc0355b27d6c9d1a679d63b6d4b4b16a9773062cb7e31192a", size = 145755, upload-time = "2026-03-04T14:17:32.664Z" } +sdist = { url = "https://files.pythonhosted.org/packages/46/e4/dbbfb2a010c4db2224a5114638acede6fe563d33cc20fb1752cebcbe6298/opentelemetry_semantic_conventions-0.66b1.tar.gz", hash = "sha256:497ca63bf383723411e8eaf60c8779e9877633c936bb641080adab59d0eb6ec8", size = 150250, upload-time = "2026-10-06T17:33:14.073Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b2/37/cc6a55e448deaa9b27377d087da8615a3416d8ad523d5960b78dbeadd02a/opentelemetry_semantic_conventions-0.61b0-py3-none-any.whl", hash = "sha256:fa530a96be229795f8cef353739b618148b0fe2b4b3f005e60e262926c4d38e2", size = 231621, upload-time = "2026-03-04T14:17:19.33Z" }, + { url = "https://files.pythonhosted.org/packages/bc/14/67f8aa798857f8cf686f515bf93d9bb877ce952ddc8efae0fa25b45ce0d6/opentelemetry_semantic_conventions-0.66b1-py3-none-any.whl", hash = "sha256:d4cddeb4315490b35213f55e2bdc9ac54bb1e4d318927475bed62b35545e581b", size = 206279, upload-time = "2026-10-06T17:32:56.103Z" }, ] [[package]] @@ -512,7 +530,7 @@ wheels = [ [[package]] name = "posthog" -version = "7.75.4" +version = "7.75.11" source = { directory = "../../" } dependencies = [ { name = "backoff" }, @@ -877,12 +895,3 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/46/78/10ad9781128ed2f99dbc474f43283b13fea8ba58723e98844367531c18e9/wrapt-1.17.3-cp314-cp314t-win_arm64.whl", hash = "sha256:f38e60678850c42461d4202739f9bf1e3a737c7ad283638251e79cc49effb6b6", size = 38471, upload-time = "2025-08-12T05:52:57.784Z" }, { url = "https://files.pythonhosted.org/packages/1f/f6/a933bd70f98e9cf3e08167fc5cd7aaaca49147e48411c0bd5ae701bb2194/wrapt-1.17.3-py3-none-any.whl", hash = "sha256:7171ae35d2c33d326ac19dd8facb1e82e5fd04ef8c6c0e394d7af55a55051c22", size = 23591, upload-time = "2025-08-12T05:53:20.674Z" }, ] - -[[package]] -name = "zipp" -version = "3.23.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166", size = 25547, upload-time = "2025-06-08T17:06:39.4Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276, upload-time = "2025-06-08T17:06:38.034Z" }, -] diff --git a/examples/example-ai-instructor/uv.lock b/examples/example-ai-instructor/uv.lock index 6a6d1eac..bde3f956 100644 --- a/examples/example-ai-instructor/uv.lock +++ b/examples/example-ai-instructor/uv.lock @@ -645,18 +645,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/58/a2/bb081bab032533a855d44de1d56f8e8426114ff1ba5d1f07a438a0a654f8/idna-3.20-py3-none-any.whl", hash = "sha256:ab7ae7122974553370f0bdb919e1a960b2cd1bc1ef0276416d896db81c14582c", size = 69583, upload-time = "2026-09-17T14:11:03.168Z" }, ] -[[package]] -name = "importlib-metadata" -version = "8.7.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "zipp" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/f3/49/3b30cad09e7771a4982d9975a8cbf64f00d4a1ececb53297f1d9a7be1b10/importlib_metadata-8.7.1.tar.gz", hash = "sha256:49fef1ae6440c182052f407c8d34a68f72efc36db9ca90dc0113398f2fdde8bb", size = 57107, upload-time = "2025-12-21T10:00:19.278Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fa/5e/f8e9a1d23b9c20a551a8a02ea3637b4642e22c2626e3a13a9a29cdea99eb/importlib_metadata-8.7.1-py3-none-any.whl", hash = "sha256:5a1f80bf1daa489495071efbb095d75a634cf28a8bc299581244063b53176151", size = 27865, upload-time = "2025-12-21T10:00:18.329Z" }, -] - [[package]] name = "instructor" version = "1.14.5" @@ -1097,50 +1085,80 @@ wheels = [ [[package]] name = "opentelemetry-api" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "importlib-metadata" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2c/1d/4049a9e8698361cc1a1aa03a6c59e4fa4c71e0c0f94a30f988a6876a2ae6/opentelemetry_api-1.40.0.tar.gz", hash = "sha256:159be641c0b04d11e9ecd576906462773eb97ae1b657730f0ecf64d32071569f", size = 70851, upload-time = "2026-03-04T14:17:21.555Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2e/02/6e0ae9cc61bd3169d401077b507b3ebc344745171e1051ab430be012dcd9/opentelemetry_api-1.45.1.tar.gz", hash = "sha256:aa38ed19bcc084ba42782a73255b3582283eced7ad6dddbd6695189e69adfb75", size = 72804, upload-time = "2026-10-06T17:32:58.133Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/41/f7dcf80b81ee8e71c1a2b59f14208bc723edbd89ed027a73b175abf6348e/opentelemetry_api-1.45.1-py3-none-any.whl", hash = "sha256:b31553efa588ae44bc306f863c785c5333a9ecc091248c6ee68b4b6c87fdedfb", size = 60256, upload-time = "2026-10-06T17:32:33.506Z" }, +] + +[[package]] +name = "opentelemetry-exporter-http-transport" +version = "0.66b1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/62/0c/e3ebdb4b507f66afcc905e6885a4946969bd75b45988492643356fbbdc63/opentelemetry_exporter_http_transport-0.66b1.tar.gz", hash = "sha256:443080203bf52586ce0b2ad901e8951c61833eab1aa539ae6f1f16fe9e8e7952", size = 11693, upload-time = "2026-10-06T17:32:59.65Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/69/6af86ff66492b481c6a4c05dcfd68beb47ed8ba046440a26a2aac76b95c7/opentelemetry_exporter_http_transport-0.66b1-py3-none-any.whl", hash = "sha256:2f95404bdee7f9d2d529c7de56c7bd86d014d774d8fbf137810e0167f8a492bf", size = 12155, upload-time = "2026-10-06T17:32:35.454Z" }, +] + +[package.optional-dependencies] +requests = [ + { name = "requests" }, +] + +[[package]] +name = "opentelemetry-exporter-otlp-common" +version = "0.66b1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-sdk" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cb/19/41de712173f43057e4532d42ece7d0c6d4210d353e5752433cb14987643f/opentelemetry_exporter_otlp_common-0.66b1.tar.gz", hash = "sha256:6b1403487a2185ac1feb45fd5546fdf8630ce71c36bcefaadf51e2130e9e23f9", size = 14325, upload-time = "2026-10-06T17:33:01.725Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5f/bf/93795954016c522008da367da292adceed71cca6ee1717e1d64c83089099/opentelemetry_api-1.40.0-py3-none-any.whl", hash = "sha256:82dd69331ae74b06f6a874704be0cfaa49a1650e1537d4a813b86ecef7d0ecf9", size = 68676, upload-time = "2026-03-04T14:17:01.24Z" }, + { url = "https://files.pythonhosted.org/packages/fc/39/8c23d67665c762aa51840fa06f86e902e8f6f1693bc8d7e3d98cd6e2f753/opentelemetry_exporter_otlp_common-0.66b1-py3-none-any.whl", hash = "sha256:00ff8592c3a7cb729ff3fdc7ffa12372c243bdf2163e80c180994d0c7bd83ee9", size = 12385, upload-time = "2026-10-06T17:32:38.177Z" }, ] [[package]] name = "opentelemetry-exporter-otlp-proto-common" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-proto" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/51/bc/1559d46557fe6eca0b46c88d4c2676285f1f3be2e8d06bb5d15fbffc814a/opentelemetry_exporter_otlp_proto_common-1.40.0.tar.gz", hash = "sha256:1cbee86a4064790b362a86601ee7934f368b81cd4cc2f2e163902a6e7818a0fa", size = 20416, upload-time = "2026-03-04T14:17:23.801Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c1/8e/65e85e5137991a3c493b11682151d198638a5bc1dd4b4c5f67e013c57d7c/opentelemetry_exporter_otlp_proto_common-1.45.1.tar.gz", hash = "sha256:2e4adcc3a67bcf57804fc49514f0ef64974ca7590aa3491da389852b4a0628f6", size = 18873, upload-time = "2026-10-06T17:33:04.471Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8b/ca/8f122055c97a932311a3f640273f084e738008933503d0c2563cd5d591fc/opentelemetry_exporter_otlp_proto_common-1.40.0-py3-none-any.whl", hash = "sha256:7081ff453835a82417bf38dccf122c827c3cbc94f2079b03bba02a3165f25149", size = 18369, upload-time = "2026-03-04T14:17:04.796Z" }, + { url = "https://files.pythonhosted.org/packages/84/aa/92f225d353904e7f70b8b3e3c1b02db0cf56f744c2e83c581dc372e78873/opentelemetry_exporter_otlp_proto_common-1.45.1-py3-none-any.whl", hash = "sha256:2f446183ae7047b036226f1d846c41a834b0e8755ad13b51a51dd38952eb466c", size = 15393, upload-time = "2026-10-06T17:32:41.911Z" }, ] [[package]] name = "opentelemetry-exporter-otlp-proto-http" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "googleapis-common-protos" }, { name = "opentelemetry-api" }, + { name = "opentelemetry-exporter-http-transport", extra = ["requests"] }, + { name = "opentelemetry-exporter-otlp-common" }, { name = "opentelemetry-exporter-otlp-proto-common" }, { name = "opentelemetry-proto" }, { name = "opentelemetry-sdk" }, { name = "requests" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2e/fa/73d50e2c15c56be4d000c98e24221d494674b0cc95524e2a8cb3856d95a4/opentelemetry_exporter_otlp_proto_http-1.40.0.tar.gz", hash = "sha256:db48f5e0f33217588bbc00274a31517ba830da576e59503507c839b38fa0869c", size = 17772, upload-time = "2026-03-04T14:17:25.324Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/17/26487707ea4caa97b17e6e4b5fa72133a53512ffa2f5cf7a49ef284b29cb/opentelemetry_exporter_otlp_proto_http-1.45.1.tar.gz", hash = "sha256:45c218405ce3fd879596924b1874bf9a8f6880206d61065c5a912c8e5c297fb7", size = 28839, upload-time = "2026-10-06T17:33:05.713Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/3a/8865d6754e61c9fb170cdd530a124a53769ee5f740236064816eb0ca7301/opentelemetry_exporter_otlp_proto_http-1.40.0-py3-none-any.whl", hash = "sha256:a8d1dab28f504c5d96577d6509f80a8150e44e8f45f82cdbe0e34c99ab040069", size = 19960, upload-time = "2026-03-04T14:17:07.153Z" }, + { url = "https://files.pythonhosted.org/packages/aa/1f/517eaa0187ba106a9da97160ce2add3a371812681dc440930b267f714e42/opentelemetry_exporter_otlp_proto_http-1.45.1-py3-none-any.whl", hash = "sha256:24a97cf3753c7fb52fad44a696e452ff371686339e2acf3309e2eda3d0230700", size = 22180, upload-time = "2026-10-06T17:32:43.946Z" }, ] [[package]] name = "opentelemetry-instrumentation" -version = "0.61b0" +version = "0.66b1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, @@ -1148,9 +1166,9 @@ dependencies = [ { name = "packaging" }, { name = "wrapt" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/da/37/6bf8e66bfcee5d3c6515b79cb2ee9ad05fe573c20f7ceb288d0e7eeec28c/opentelemetry_instrumentation-0.61b0.tar.gz", hash = "sha256:cb21b48db738c9de196eba6b805b4ff9de3b7f187e4bbf9a466fa170514f1fc7", size = 32606, upload-time = "2026-03-04T14:20:16.825Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a5/03/89e47ff8d52a4f83b343e6eb9ef1698ff45357216e5b6b2b21e0da5c5c7d/opentelemetry_instrumentation-0.66b1.tar.gz", hash = "sha256:e79a510f7d87c72d95e964ddb42193a0d9a75668c027d980eab032ea1322a5ce", size = 43308, upload-time = "2026-10-06T17:36:10.703Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d8/3e/f6f10f178b6316de67f0dfdbbb699a24fbe8917cf1743c1595fb9dcdd461/opentelemetry_instrumentation-0.61b0-py3-none-any.whl", hash = "sha256:92a93a280e69788e8f88391247cc530fd81f16f2b011979d4d6398f805cfbc63", size = 33448, upload-time = "2026-03-04T14:19:02.447Z" }, + { url = "https://files.pythonhosted.org/packages/da/b2/d1413681ff43e13ac9860df27e1226d3199ab0b97b352ceea41abcc660a5/opentelemetry_instrumentation-0.66b1-py3-none-any.whl", hash = "sha256:4c4aa14dc9a24a02325a9d4c42c4d0208dbb1374c2b1b8fe6c9392d59f3e1008", size = 36904, upload-time = "2026-10-06T17:35:11.663Z" }, ] [[package]] @@ -1169,41 +1187,41 @@ wheels = [ [[package]] name = "opentelemetry-proto" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "protobuf" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4c/77/dd38991db037fdfce45849491cb61de5ab000f49824a00230afb112a4392/opentelemetry_proto-1.40.0.tar.gz", hash = "sha256:03f639ca129ba513f5819810f5b1f42bcb371391405d99c168fe6937c62febcd", size = 45667, upload-time = "2026-03-04T14:17:31.194Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4b/7f/15f014fb195da6c2dbb6c71399b8e76824878718e94de6454038488eed28/opentelemetry_proto-1.45.1.tar.gz", hash = "sha256:79e0fb95e4616691a469439238aa9224d75779b3e108e895d1aa125ab29ca77c", size = 46488, upload-time = "2026-10-06T17:33:11.49Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b9/b2/189b2577dde745b15625b3214302605b1353436219d42b7912e77fa8dc24/opentelemetry_proto-1.40.0-py3-none-any.whl", hash = "sha256:266c4385d88923a23d63e353e9761af0f47a6ed0d486979777fe4de59dc9b25f", size = 72073, upload-time = "2026-03-04T14:17:16.673Z" }, + { url = "https://files.pythonhosted.org/packages/ab/9a/42ec8180a769516ae757e893b69736826efceac7332553915b4528a91c6d/opentelemetry_proto-1.45.1-py3-none-any.whl", hash = "sha256:f38e2a8413053c180cd3d2637fbb279673ec2f6a6e09c995aafa2f452c52b46e", size = 72488, upload-time = "2026-10-06T17:32:53.057Z" }, ] [[package]] name = "opentelemetry-sdk" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, { name = "opentelemetry-semantic-conventions" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/58/fd/3c3125b20ba18ce2155ba9ea74acb0ae5d25f8cd39cfd37455601b7955cc/opentelemetry_sdk-1.40.0.tar.gz", hash = "sha256:18e9f5ec20d859d268c7cb3c5198c8d105d073714db3de50b593b8c1345a48f2", size = 184252, upload-time = "2026-03-04T14:17:31.87Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/79/7392e21a1c8f0c61d90b223e31c7e48cb9d452e91a6b820ad24cca5f23c4/opentelemetry_sdk-1.45.1.tar.gz", hash = "sha256:63d24a6ca645019a631e6a51999c73e93adcac1196ca640b8ae78a7cc4762bf3", size = 218324, upload-time = "2026-10-06T17:33:13.26Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/c5/6a852903d8bfac758c6dc6e9a68b015d3c33f2f1be5e9591e0f4b69c7e0a/opentelemetry_sdk-1.40.0-py3-none-any.whl", hash = "sha256:787d2154a71f4b3d81f20524a8ce061b7db667d24e46753f32a7bc48f1c1f3f1", size = 141951, upload-time = "2026-03-04T14:17:17.961Z" }, + { url = "https://files.pythonhosted.org/packages/95/3c/87c42b4bd6dd297536f04cd9383d212ac557ecd49f2cbdcd46da1c9ef5c8/opentelemetry_sdk-1.45.1-py3-none-any.whl", hash = "sha256:c604c11dc429810812348989115fa44bd558772a3d7442afc43d024f2c250ca4", size = 140063, upload-time = "2026-10-06T17:32:55.04Z" }, ] [[package]] name = "opentelemetry-semantic-conventions" -version = "0.61b0" +version = "0.66b1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6d/c0/4ae7973f3c2cfd2b6e321f1675626f0dab0a97027cc7a297474c9c8f3d04/opentelemetry_semantic_conventions-0.61b0.tar.gz", hash = "sha256:072f65473c5d7c6dc0355b27d6c9d1a679d63b6d4b4b16a9773062cb7e31192a", size = 145755, upload-time = "2026-03-04T14:17:32.664Z" } +sdist = { url = "https://files.pythonhosted.org/packages/46/e4/dbbfb2a010c4db2224a5114638acede6fe563d33cc20fb1752cebcbe6298/opentelemetry_semantic_conventions-0.66b1.tar.gz", hash = "sha256:497ca63bf383723411e8eaf60c8779e9877633c936bb641080adab59d0eb6ec8", size = 150250, upload-time = "2026-10-06T17:33:14.073Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b2/37/cc6a55e448deaa9b27377d087da8615a3416d8ad523d5960b78dbeadd02a/opentelemetry_semantic_conventions-0.61b0-py3-none-any.whl", hash = "sha256:fa530a96be229795f8cef353739b618148b0fe2b4b3f005e60e262926c4d38e2", size = 231621, upload-time = "2026-03-04T14:17:19.33Z" }, + { url = "https://files.pythonhosted.org/packages/bc/14/67f8aa798857f8cf686f515bf93d9bb877ce952ddc8efae0fa25b45ce0d6/opentelemetry_semantic_conventions-0.66b1-py3-none-any.whl", hash = "sha256:d4cddeb4315490b35213f55e2bdc9ac54bb1e4d318927475bed62b35545e581b", size = 206279, upload-time = "2026-10-06T17:32:56.103Z" }, ] [[package]] @@ -1217,7 +1235,7 @@ wheels = [ [[package]] name = "posthog" -version = "7.75.4" +version = "7.75.11" source = { directory = "../../" } dependencies = [ { name = "backoff" }, @@ -1901,12 +1919,3 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/88/91/41e284ca2cf5211e05dae031d126a3668aea88fa759df56e7e35c6ad25ba/yarl-1.25.1-cp315-cp315t-win_arm64.whl", hash = "sha256:783dd1467083f4d3f7722ad6a313f24c173e7571372738fcb7a6e6d1ba48df25", size = 101804, upload-time = "2026-09-15T19:34:57.231Z" }, { url = "https://files.pythonhosted.org/packages/54/22/318c7980066769c6bcd9221ed2248294f5698811da099013098c670565ed/yarl-1.25.1-py3-none-any.whl", hash = "sha256:681c758b0490f9e96b78e5fa8e8dc6e648e9185bb6eaebe73183c33ea0c445f3", size = 63617, upload-time = "2026-09-15T19:34:59.616Z" }, ] - -[[package]] -name = "zipp" -version = "3.23.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166", size = 25547, upload-time = "2025-06-08T17:06:39.4Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276, upload-time = "2025-06-08T17:06:38.034Z" }, -] diff --git a/examples/example-ai-langchain/uv.lock b/examples/example-ai-langchain/uv.lock index d7238975..00c05f8e 100644 --- a/examples/example-ai-langchain/uv.lock +++ b/examples/example-ai-langchain/uv.lock @@ -264,18 +264,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/58/a2/bb081bab032533a855d44de1d56f8e8426114ff1ba5d1f07a438a0a654f8/idna-3.20-py3-none-any.whl", hash = "sha256:ab7ae7122974553370f0bdb919e1a960b2cd1bc1ef0276416d896db81c14582c", size = 69583, upload-time = "2026-09-17T14:11:03.168Z" }, ] -[[package]] -name = "importlib-metadata" -version = "8.7.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "zipp" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/f3/49/3b30cad09e7771a4982d9975a8cbf64f00d4a1ececb53297f1d9a7be1b10/importlib_metadata-8.7.1.tar.gz", hash = "sha256:49fef1ae6440c182052f407c8d34a68f72efc36db9ca90dc0113398f2fdde8bb", size = 57107, upload-time = "2025-12-21T10:00:19.278Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fa/5e/f8e9a1d23b9c20a551a8a02ea3637b4642e22c2626e3a13a9a29cdea99eb/importlib_metadata-8.7.1-py3-none-any.whl", hash = "sha256:5a1f80bf1daa489495071efbb095d75a634cf28a8bc299581244063b53176151", size = 27865, upload-time = "2025-12-21T10:00:18.329Z" }, -] - [[package]] name = "jiter" version = "0.13.0" @@ -559,50 +547,80 @@ wheels = [ [[package]] name = "opentelemetry-api" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "importlib-metadata" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2c/1d/4049a9e8698361cc1a1aa03a6c59e4fa4c71e0c0f94a30f988a6876a2ae6/opentelemetry_api-1.40.0.tar.gz", hash = "sha256:159be641c0b04d11e9ecd576906462773eb97ae1b657730f0ecf64d32071569f", size = 70851, upload-time = "2026-03-04T14:17:21.555Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2e/02/6e0ae9cc61bd3169d401077b507b3ebc344745171e1051ab430be012dcd9/opentelemetry_api-1.45.1.tar.gz", hash = "sha256:aa38ed19bcc084ba42782a73255b3582283eced7ad6dddbd6695189e69adfb75", size = 72804, upload-time = "2026-10-06T17:32:58.133Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/41/f7dcf80b81ee8e71c1a2b59f14208bc723edbd89ed027a73b175abf6348e/opentelemetry_api-1.45.1-py3-none-any.whl", hash = "sha256:b31553efa588ae44bc306f863c785c5333a9ecc091248c6ee68b4b6c87fdedfb", size = 60256, upload-time = "2026-10-06T17:32:33.506Z" }, +] + +[[package]] +name = "opentelemetry-exporter-http-transport" +version = "0.66b1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/62/0c/e3ebdb4b507f66afcc905e6885a4946969bd75b45988492643356fbbdc63/opentelemetry_exporter_http_transport-0.66b1.tar.gz", hash = "sha256:443080203bf52586ce0b2ad901e8951c61833eab1aa539ae6f1f16fe9e8e7952", size = 11693, upload-time = "2026-10-06T17:32:59.65Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/69/6af86ff66492b481c6a4c05dcfd68beb47ed8ba046440a26a2aac76b95c7/opentelemetry_exporter_http_transport-0.66b1-py3-none-any.whl", hash = "sha256:2f95404bdee7f9d2d529c7de56c7bd86d014d774d8fbf137810e0167f8a492bf", size = 12155, upload-time = "2026-10-06T17:32:35.454Z" }, +] + +[package.optional-dependencies] +requests = [ + { name = "requests" }, +] + +[[package]] +name = "opentelemetry-exporter-otlp-common" +version = "0.66b1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-sdk" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cb/19/41de712173f43057e4532d42ece7d0c6d4210d353e5752433cb14987643f/opentelemetry_exporter_otlp_common-0.66b1.tar.gz", hash = "sha256:6b1403487a2185ac1feb45fd5546fdf8630ce71c36bcefaadf51e2130e9e23f9", size = 14325, upload-time = "2026-10-06T17:33:01.725Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5f/bf/93795954016c522008da367da292adceed71cca6ee1717e1d64c83089099/opentelemetry_api-1.40.0-py3-none-any.whl", hash = "sha256:82dd69331ae74b06f6a874704be0cfaa49a1650e1537d4a813b86ecef7d0ecf9", size = 68676, upload-time = "2026-03-04T14:17:01.24Z" }, + { url = "https://files.pythonhosted.org/packages/fc/39/8c23d67665c762aa51840fa06f86e902e8f6f1693bc8d7e3d98cd6e2f753/opentelemetry_exporter_otlp_common-0.66b1-py3-none-any.whl", hash = "sha256:00ff8592c3a7cb729ff3fdc7ffa12372c243bdf2163e80c180994d0c7bd83ee9", size = 12385, upload-time = "2026-10-06T17:32:38.177Z" }, ] [[package]] name = "opentelemetry-exporter-otlp-proto-common" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-proto" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/51/bc/1559d46557fe6eca0b46c88d4c2676285f1f3be2e8d06bb5d15fbffc814a/opentelemetry_exporter_otlp_proto_common-1.40.0.tar.gz", hash = "sha256:1cbee86a4064790b362a86601ee7934f368b81cd4cc2f2e163902a6e7818a0fa", size = 20416, upload-time = "2026-03-04T14:17:23.801Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c1/8e/65e85e5137991a3c493b11682151d198638a5bc1dd4b4c5f67e013c57d7c/opentelemetry_exporter_otlp_proto_common-1.45.1.tar.gz", hash = "sha256:2e4adcc3a67bcf57804fc49514f0ef64974ca7590aa3491da389852b4a0628f6", size = 18873, upload-time = "2026-10-06T17:33:04.471Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8b/ca/8f122055c97a932311a3f640273f084e738008933503d0c2563cd5d591fc/opentelemetry_exporter_otlp_proto_common-1.40.0-py3-none-any.whl", hash = "sha256:7081ff453835a82417bf38dccf122c827c3cbc94f2079b03bba02a3165f25149", size = 18369, upload-time = "2026-03-04T14:17:04.796Z" }, + { url = "https://files.pythonhosted.org/packages/84/aa/92f225d353904e7f70b8b3e3c1b02db0cf56f744c2e83c581dc372e78873/opentelemetry_exporter_otlp_proto_common-1.45.1-py3-none-any.whl", hash = "sha256:2f446183ae7047b036226f1d846c41a834b0e8755ad13b51a51dd38952eb466c", size = 15393, upload-time = "2026-10-06T17:32:41.911Z" }, ] [[package]] name = "opentelemetry-exporter-otlp-proto-http" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "googleapis-common-protos" }, { name = "opentelemetry-api" }, + { name = "opentelemetry-exporter-http-transport", extra = ["requests"] }, + { name = "opentelemetry-exporter-otlp-common" }, { name = "opentelemetry-exporter-otlp-proto-common" }, { name = "opentelemetry-proto" }, { name = "opentelemetry-sdk" }, { name = "requests" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2e/fa/73d50e2c15c56be4d000c98e24221d494674b0cc95524e2a8cb3856d95a4/opentelemetry_exporter_otlp_proto_http-1.40.0.tar.gz", hash = "sha256:db48f5e0f33217588bbc00274a31517ba830da576e59503507c839b38fa0869c", size = 17772, upload-time = "2026-03-04T14:17:25.324Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/17/26487707ea4caa97b17e6e4b5fa72133a53512ffa2f5cf7a49ef284b29cb/opentelemetry_exporter_otlp_proto_http-1.45.1.tar.gz", hash = "sha256:45c218405ce3fd879596924b1874bf9a8f6880206d61065c5a912c8e5c297fb7", size = 28839, upload-time = "2026-10-06T17:33:05.713Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/3a/8865d6754e61c9fb170cdd530a124a53769ee5f740236064816eb0ca7301/opentelemetry_exporter_otlp_proto_http-1.40.0-py3-none-any.whl", hash = "sha256:a8d1dab28f504c5d96577d6509f80a8150e44e8f45f82cdbe0e34c99ab040069", size = 19960, upload-time = "2026-03-04T14:17:07.153Z" }, + { url = "https://files.pythonhosted.org/packages/aa/1f/517eaa0187ba106a9da97160ce2add3a371812681dc440930b267f714e42/opentelemetry_exporter_otlp_proto_http-1.45.1-py3-none-any.whl", hash = "sha256:24a97cf3753c7fb52fad44a696e452ff371686339e2acf3309e2eda3d0230700", size = 22180, upload-time = "2026-10-06T17:32:43.946Z" }, ] [[package]] name = "opentelemetry-instrumentation" -version = "0.61b0" +version = "0.66b1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, @@ -610,9 +628,9 @@ dependencies = [ { name = "packaging" }, { name = "wrapt" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/da/37/6bf8e66bfcee5d3c6515b79cb2ee9ad05fe573c20f7ceb288d0e7eeec28c/opentelemetry_instrumentation-0.61b0.tar.gz", hash = "sha256:cb21b48db738c9de196eba6b805b4ff9de3b7f187e4bbf9a466fa170514f1fc7", size = 32606, upload-time = "2026-03-04T14:20:16.825Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a5/03/89e47ff8d52a4f83b343e6eb9ef1698ff45357216e5b6b2b21e0da5c5c7d/opentelemetry_instrumentation-0.66b1.tar.gz", hash = "sha256:e79a510f7d87c72d95e964ddb42193a0d9a75668c027d980eab032ea1322a5ce", size = 43308, upload-time = "2026-10-06T17:36:10.703Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d8/3e/f6f10f178b6316de67f0dfdbbb699a24fbe8917cf1743c1595fb9dcdd461/opentelemetry_instrumentation-0.61b0-py3-none-any.whl", hash = "sha256:92a93a280e69788e8f88391247cc530fd81f16f2b011979d4d6398f805cfbc63", size = 33448, upload-time = "2026-03-04T14:19:02.447Z" }, + { url = "https://files.pythonhosted.org/packages/da/b2/d1413681ff43e13ac9860df27e1226d3199ab0b97b352ceea41abcc660a5/opentelemetry_instrumentation-0.66b1-py3-none-any.whl", hash = "sha256:4c4aa14dc9a24a02325a9d4c42c4d0208dbb1374c2b1b8fe6c9392d59f3e1008", size = 36904, upload-time = "2026-10-06T17:35:11.663Z" }, ] [[package]] @@ -632,41 +650,41 @@ wheels = [ [[package]] name = "opentelemetry-proto" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "protobuf" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4c/77/dd38991db037fdfce45849491cb61de5ab000f49824a00230afb112a4392/opentelemetry_proto-1.40.0.tar.gz", hash = "sha256:03f639ca129ba513f5819810f5b1f42bcb371391405d99c168fe6937c62febcd", size = 45667, upload-time = "2026-03-04T14:17:31.194Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4b/7f/15f014fb195da6c2dbb6c71399b8e76824878718e94de6454038488eed28/opentelemetry_proto-1.45.1.tar.gz", hash = "sha256:79e0fb95e4616691a469439238aa9224d75779b3e108e895d1aa125ab29ca77c", size = 46488, upload-time = "2026-10-06T17:33:11.49Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b9/b2/189b2577dde745b15625b3214302605b1353436219d42b7912e77fa8dc24/opentelemetry_proto-1.40.0-py3-none-any.whl", hash = "sha256:266c4385d88923a23d63e353e9761af0f47a6ed0d486979777fe4de59dc9b25f", size = 72073, upload-time = "2026-03-04T14:17:16.673Z" }, + { url = "https://files.pythonhosted.org/packages/ab/9a/42ec8180a769516ae757e893b69736826efceac7332553915b4528a91c6d/opentelemetry_proto-1.45.1-py3-none-any.whl", hash = "sha256:f38e2a8413053c180cd3d2637fbb279673ec2f6a6e09c995aafa2f452c52b46e", size = 72488, upload-time = "2026-10-06T17:32:53.057Z" }, ] [[package]] name = "opentelemetry-sdk" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, { name = "opentelemetry-semantic-conventions" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/58/fd/3c3125b20ba18ce2155ba9ea74acb0ae5d25f8cd39cfd37455601b7955cc/opentelemetry_sdk-1.40.0.tar.gz", hash = "sha256:18e9f5ec20d859d268c7cb3c5198c8d105d073714db3de50b593b8c1345a48f2", size = 184252, upload-time = "2026-03-04T14:17:31.87Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/79/7392e21a1c8f0c61d90b223e31c7e48cb9d452e91a6b820ad24cca5f23c4/opentelemetry_sdk-1.45.1.tar.gz", hash = "sha256:63d24a6ca645019a631e6a51999c73e93adcac1196ca640b8ae78a7cc4762bf3", size = 218324, upload-time = "2026-10-06T17:33:13.26Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/c5/6a852903d8bfac758c6dc6e9a68b015d3c33f2f1be5e9591e0f4b69c7e0a/opentelemetry_sdk-1.40.0-py3-none-any.whl", hash = "sha256:787d2154a71f4b3d81f20524a8ce061b7db667d24e46753f32a7bc48f1c1f3f1", size = 141951, upload-time = "2026-03-04T14:17:17.961Z" }, + { url = "https://files.pythonhosted.org/packages/95/3c/87c42b4bd6dd297536f04cd9383d212ac557ecd49f2cbdcd46da1c9ef5c8/opentelemetry_sdk-1.45.1-py3-none-any.whl", hash = "sha256:c604c11dc429810812348989115fa44bd558772a3d7442afc43d024f2c250ca4", size = 140063, upload-time = "2026-10-06T17:32:55.04Z" }, ] [[package]] name = "opentelemetry-semantic-conventions" -version = "0.61b0" +version = "0.66b1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6d/c0/4ae7973f3c2cfd2b6e321f1675626f0dab0a97027cc7a297474c9c8f3d04/opentelemetry_semantic_conventions-0.61b0.tar.gz", hash = "sha256:072f65473c5d7c6dc0355b27d6c9d1a679d63b6d4b4b16a9773062cb7e31192a", size = 145755, upload-time = "2026-03-04T14:17:32.664Z" } +sdist = { url = "https://files.pythonhosted.org/packages/46/e4/dbbfb2a010c4db2224a5114638acede6fe563d33cc20fb1752cebcbe6298/opentelemetry_semantic_conventions-0.66b1.tar.gz", hash = "sha256:497ca63bf383723411e8eaf60c8779e9877633c936bb641080adab59d0eb6ec8", size = 150250, upload-time = "2026-10-06T17:33:14.073Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b2/37/cc6a55e448deaa9b27377d087da8615a3416d8ad523d5960b78dbeadd02a/opentelemetry_semantic_conventions-0.61b0-py3-none-any.whl", hash = "sha256:fa530a96be229795f8cef353739b618148b0fe2b4b3f005e60e262926c4d38e2", size = 231621, upload-time = "2026-03-04T14:17:19.33Z" }, + { url = "https://files.pythonhosted.org/packages/bc/14/67f8aa798857f8cf686f515bf93d9bb877ce952ddc8efae0fa25b45ce0d6/opentelemetry_semantic_conventions-0.66b1-py3-none-any.whl", hash = "sha256:d4cddeb4315490b35213f55e2bdc9ac54bb1e4d318927475bed62b35545e581b", size = 206279, upload-time = "2026-10-06T17:32:56.103Z" }, ] [[package]] @@ -830,7 +848,7 @@ wheels = [ [[package]] name = "posthog" -version = "7.75.4" +version = "7.75.11" source = { directory = "../../" } dependencies = [ { name = "backoff" }, @@ -1669,15 +1687,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7b/d9/8d95e906764a386a3d3b596f3c68bb63687dfca806373509f51ce8eea81f/xxhash-3.6.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:15e0dac10eb9309508bfc41f7f9deaa7755c69e35af835db9cb10751adebc35d", size = 31565, upload-time = "2025-10-02T14:37:06.966Z" }, ] -[[package]] -name = "zipp" -version = "3.23.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166", size = 25547, upload-time = "2025-06-08T17:06:39.4Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276, upload-time = "2025-06-08T17:06:38.034Z" }, -] - [[package]] name = "zstandard" version = "0.25.0" diff --git a/examples/example-ai-langgraph/uv.lock b/examples/example-ai-langgraph/uv.lock index 0fc33026..436b5d46 100644 --- a/examples/example-ai-langgraph/uv.lock +++ b/examples/example-ai-langgraph/uv.lock @@ -268,18 +268,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/58/a2/bb081bab032533a855d44de1d56f8e8426114ff1ba5d1f07a438a0a654f8/idna-3.20-py3-none-any.whl", hash = "sha256:ab7ae7122974553370f0bdb919e1a960b2cd1bc1ef0276416d896db81c14582c", size = 69583, upload-time = "2026-09-17T14:11:03.168Z" }, ] -[[package]] -name = "importlib-metadata" -version = "8.7.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "zipp" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/f3/49/3b30cad09e7771a4982d9975a8cbf64f00d4a1ececb53297f1d9a7be1b10/importlib_metadata-8.7.1.tar.gz", hash = "sha256:49fef1ae6440c182052f407c8d34a68f72efc36db9ca90dc0113398f2fdde8bb", size = 57107, upload-time = "2025-12-21T10:00:19.278Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fa/5e/f8e9a1d23b9c20a551a8a02ea3637b4642e22c2626e3a13a9a29cdea99eb/importlib_metadata-8.7.1-py3-none-any.whl", hash = "sha256:5a1f80bf1daa489495071efbb095d75a634cf28a8bc299581244063b53176151", size = 27865, upload-time = "2025-12-21T10:00:18.329Z" }, -] - [[package]] name = "jiter" version = "0.13.0" @@ -545,50 +533,80 @@ wheels = [ [[package]] name = "opentelemetry-api" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "importlib-metadata" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2c/1d/4049a9e8698361cc1a1aa03a6c59e4fa4c71e0c0f94a30f988a6876a2ae6/opentelemetry_api-1.40.0.tar.gz", hash = "sha256:159be641c0b04d11e9ecd576906462773eb97ae1b657730f0ecf64d32071569f", size = 70851, upload-time = "2026-03-04T14:17:21.555Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2e/02/6e0ae9cc61bd3169d401077b507b3ebc344745171e1051ab430be012dcd9/opentelemetry_api-1.45.1.tar.gz", hash = "sha256:aa38ed19bcc084ba42782a73255b3582283eced7ad6dddbd6695189e69adfb75", size = 72804, upload-time = "2026-10-06T17:32:58.133Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/41/f7dcf80b81ee8e71c1a2b59f14208bc723edbd89ed027a73b175abf6348e/opentelemetry_api-1.45.1-py3-none-any.whl", hash = "sha256:b31553efa588ae44bc306f863c785c5333a9ecc091248c6ee68b4b6c87fdedfb", size = 60256, upload-time = "2026-10-06T17:32:33.506Z" }, +] + +[[package]] +name = "opentelemetry-exporter-http-transport" +version = "0.66b1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/62/0c/e3ebdb4b507f66afcc905e6885a4946969bd75b45988492643356fbbdc63/opentelemetry_exporter_http_transport-0.66b1.tar.gz", hash = "sha256:443080203bf52586ce0b2ad901e8951c61833eab1aa539ae6f1f16fe9e8e7952", size = 11693, upload-time = "2026-10-06T17:32:59.65Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/69/6af86ff66492b481c6a4c05dcfd68beb47ed8ba046440a26a2aac76b95c7/opentelemetry_exporter_http_transport-0.66b1-py3-none-any.whl", hash = "sha256:2f95404bdee7f9d2d529c7de56c7bd86d014d774d8fbf137810e0167f8a492bf", size = 12155, upload-time = "2026-10-06T17:32:35.454Z" }, +] + +[package.optional-dependencies] +requests = [ + { name = "requests" }, +] + +[[package]] +name = "opentelemetry-exporter-otlp-common" +version = "0.66b1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-sdk" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cb/19/41de712173f43057e4532d42ece7d0c6d4210d353e5752433cb14987643f/opentelemetry_exporter_otlp_common-0.66b1.tar.gz", hash = "sha256:6b1403487a2185ac1feb45fd5546fdf8630ce71c36bcefaadf51e2130e9e23f9", size = 14325, upload-time = "2026-10-06T17:33:01.725Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5f/bf/93795954016c522008da367da292adceed71cca6ee1717e1d64c83089099/opentelemetry_api-1.40.0-py3-none-any.whl", hash = "sha256:82dd69331ae74b06f6a874704be0cfaa49a1650e1537d4a813b86ecef7d0ecf9", size = 68676, upload-time = "2026-03-04T14:17:01.24Z" }, + { url = "https://files.pythonhosted.org/packages/fc/39/8c23d67665c762aa51840fa06f86e902e8f6f1693bc8d7e3d98cd6e2f753/opentelemetry_exporter_otlp_common-0.66b1-py3-none-any.whl", hash = "sha256:00ff8592c3a7cb729ff3fdc7ffa12372c243bdf2163e80c180994d0c7bd83ee9", size = 12385, upload-time = "2026-10-06T17:32:38.177Z" }, ] [[package]] name = "opentelemetry-exporter-otlp-proto-common" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-proto" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/51/bc/1559d46557fe6eca0b46c88d4c2676285f1f3be2e8d06bb5d15fbffc814a/opentelemetry_exporter_otlp_proto_common-1.40.0.tar.gz", hash = "sha256:1cbee86a4064790b362a86601ee7934f368b81cd4cc2f2e163902a6e7818a0fa", size = 20416, upload-time = "2026-03-04T14:17:23.801Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c1/8e/65e85e5137991a3c493b11682151d198638a5bc1dd4b4c5f67e013c57d7c/opentelemetry_exporter_otlp_proto_common-1.45.1.tar.gz", hash = "sha256:2e4adcc3a67bcf57804fc49514f0ef64974ca7590aa3491da389852b4a0628f6", size = 18873, upload-time = "2026-10-06T17:33:04.471Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8b/ca/8f122055c97a932311a3f640273f084e738008933503d0c2563cd5d591fc/opentelemetry_exporter_otlp_proto_common-1.40.0-py3-none-any.whl", hash = "sha256:7081ff453835a82417bf38dccf122c827c3cbc94f2079b03bba02a3165f25149", size = 18369, upload-time = "2026-03-04T14:17:04.796Z" }, + { url = "https://files.pythonhosted.org/packages/84/aa/92f225d353904e7f70b8b3e3c1b02db0cf56f744c2e83c581dc372e78873/opentelemetry_exporter_otlp_proto_common-1.45.1-py3-none-any.whl", hash = "sha256:2f446183ae7047b036226f1d846c41a834b0e8755ad13b51a51dd38952eb466c", size = 15393, upload-time = "2026-10-06T17:32:41.911Z" }, ] [[package]] name = "opentelemetry-exporter-otlp-proto-http" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "googleapis-common-protos" }, { name = "opentelemetry-api" }, + { name = "opentelemetry-exporter-http-transport", extra = ["requests"] }, + { name = "opentelemetry-exporter-otlp-common" }, { name = "opentelemetry-exporter-otlp-proto-common" }, { name = "opentelemetry-proto" }, { name = "opentelemetry-sdk" }, { name = "requests" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2e/fa/73d50e2c15c56be4d000c98e24221d494674b0cc95524e2a8cb3856d95a4/opentelemetry_exporter_otlp_proto_http-1.40.0.tar.gz", hash = "sha256:db48f5e0f33217588bbc00274a31517ba830da576e59503507c839b38fa0869c", size = 17772, upload-time = "2026-03-04T14:17:25.324Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/17/26487707ea4caa97b17e6e4b5fa72133a53512ffa2f5cf7a49ef284b29cb/opentelemetry_exporter_otlp_proto_http-1.45.1.tar.gz", hash = "sha256:45c218405ce3fd879596924b1874bf9a8f6880206d61065c5a912c8e5c297fb7", size = 28839, upload-time = "2026-10-06T17:33:05.713Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/3a/8865d6754e61c9fb170cdd530a124a53769ee5f740236064816eb0ca7301/opentelemetry_exporter_otlp_proto_http-1.40.0-py3-none-any.whl", hash = "sha256:a8d1dab28f504c5d96577d6509f80a8150e44e8f45f82cdbe0e34c99ab040069", size = 19960, upload-time = "2026-03-04T14:17:07.153Z" }, + { url = "https://files.pythonhosted.org/packages/aa/1f/517eaa0187ba106a9da97160ce2add3a371812681dc440930b267f714e42/opentelemetry_exporter_otlp_proto_http-1.45.1-py3-none-any.whl", hash = "sha256:24a97cf3753c7fb52fad44a696e452ff371686339e2acf3309e2eda3d0230700", size = 22180, upload-time = "2026-10-06T17:32:43.946Z" }, ] [[package]] name = "opentelemetry-instrumentation" -version = "0.61b0" +version = "0.66b1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, @@ -596,9 +614,9 @@ dependencies = [ { name = "packaging" }, { name = "wrapt" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/da/37/6bf8e66bfcee5d3c6515b79cb2ee9ad05fe573c20f7ceb288d0e7eeec28c/opentelemetry_instrumentation-0.61b0.tar.gz", hash = "sha256:cb21b48db738c9de196eba6b805b4ff9de3b7f187e4bbf9a466fa170514f1fc7", size = 32606, upload-time = "2026-03-04T14:20:16.825Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a5/03/89e47ff8d52a4f83b343e6eb9ef1698ff45357216e5b6b2b21e0da5c5c7d/opentelemetry_instrumentation-0.66b1.tar.gz", hash = "sha256:e79a510f7d87c72d95e964ddb42193a0d9a75668c027d980eab032ea1322a5ce", size = 43308, upload-time = "2026-10-06T17:36:10.703Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d8/3e/f6f10f178b6316de67f0dfdbbb699a24fbe8917cf1743c1595fb9dcdd461/opentelemetry_instrumentation-0.61b0-py3-none-any.whl", hash = "sha256:92a93a280e69788e8f88391247cc530fd81f16f2b011979d4d6398f805cfbc63", size = 33448, upload-time = "2026-03-04T14:19:02.447Z" }, + { url = "https://files.pythonhosted.org/packages/da/b2/d1413681ff43e13ac9860df27e1226d3199ab0b97b352ceea41abcc660a5/opentelemetry_instrumentation-0.66b1-py3-none-any.whl", hash = "sha256:4c4aa14dc9a24a02325a9d4c42c4d0208dbb1374c2b1b8fe6c9392d59f3e1008", size = 36904, upload-time = "2026-10-06T17:35:11.663Z" }, ] [[package]] @@ -618,41 +636,41 @@ wheels = [ [[package]] name = "opentelemetry-proto" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "protobuf" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4c/77/dd38991db037fdfce45849491cb61de5ab000f49824a00230afb112a4392/opentelemetry_proto-1.40.0.tar.gz", hash = "sha256:03f639ca129ba513f5819810f5b1f42bcb371391405d99c168fe6937c62febcd", size = 45667, upload-time = "2026-03-04T14:17:31.194Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4b/7f/15f014fb195da6c2dbb6c71399b8e76824878718e94de6454038488eed28/opentelemetry_proto-1.45.1.tar.gz", hash = "sha256:79e0fb95e4616691a469439238aa9224d75779b3e108e895d1aa125ab29ca77c", size = 46488, upload-time = "2026-10-06T17:33:11.49Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b9/b2/189b2577dde745b15625b3214302605b1353436219d42b7912e77fa8dc24/opentelemetry_proto-1.40.0-py3-none-any.whl", hash = "sha256:266c4385d88923a23d63e353e9761af0f47a6ed0d486979777fe4de59dc9b25f", size = 72073, upload-time = "2026-03-04T14:17:16.673Z" }, + { url = "https://files.pythonhosted.org/packages/ab/9a/42ec8180a769516ae757e893b69736826efceac7332553915b4528a91c6d/opentelemetry_proto-1.45.1-py3-none-any.whl", hash = "sha256:f38e2a8413053c180cd3d2637fbb279673ec2f6a6e09c995aafa2f452c52b46e", size = 72488, upload-time = "2026-10-06T17:32:53.057Z" }, ] [[package]] name = "opentelemetry-sdk" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, { name = "opentelemetry-semantic-conventions" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/58/fd/3c3125b20ba18ce2155ba9ea74acb0ae5d25f8cd39cfd37455601b7955cc/opentelemetry_sdk-1.40.0.tar.gz", hash = "sha256:18e9f5ec20d859d268c7cb3c5198c8d105d073714db3de50b593b8c1345a48f2", size = 184252, upload-time = "2026-03-04T14:17:31.87Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/79/7392e21a1c8f0c61d90b223e31c7e48cb9d452e91a6b820ad24cca5f23c4/opentelemetry_sdk-1.45.1.tar.gz", hash = "sha256:63d24a6ca645019a631e6a51999c73e93adcac1196ca640b8ae78a7cc4762bf3", size = 218324, upload-time = "2026-10-06T17:33:13.26Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/c5/6a852903d8bfac758c6dc6e9a68b015d3c33f2f1be5e9591e0f4b69c7e0a/opentelemetry_sdk-1.40.0-py3-none-any.whl", hash = "sha256:787d2154a71f4b3d81f20524a8ce061b7db667d24e46753f32a7bc48f1c1f3f1", size = 141951, upload-time = "2026-03-04T14:17:17.961Z" }, + { url = "https://files.pythonhosted.org/packages/95/3c/87c42b4bd6dd297536f04cd9383d212ac557ecd49f2cbdcd46da1c9ef5c8/opentelemetry_sdk-1.45.1-py3-none-any.whl", hash = "sha256:c604c11dc429810812348989115fa44bd558772a3d7442afc43d024f2c250ca4", size = 140063, upload-time = "2026-10-06T17:32:55.04Z" }, ] [[package]] name = "opentelemetry-semantic-conventions" -version = "0.61b0" +version = "0.66b1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6d/c0/4ae7973f3c2cfd2b6e321f1675626f0dab0a97027cc7a297474c9c8f3d04/opentelemetry_semantic_conventions-0.61b0.tar.gz", hash = "sha256:072f65473c5d7c6dc0355b27d6c9d1a679d63b6d4b4b16a9773062cb7e31192a", size = 145755, upload-time = "2026-03-04T14:17:32.664Z" } +sdist = { url = "https://files.pythonhosted.org/packages/46/e4/dbbfb2a010c4db2224a5114638acede6fe563d33cc20fb1752cebcbe6298/opentelemetry_semantic_conventions-0.66b1.tar.gz", hash = "sha256:497ca63bf383723411e8eaf60c8779e9877633c936bb641080adab59d0eb6ec8", size = 150250, upload-time = "2026-10-06T17:33:14.073Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b2/37/cc6a55e448deaa9b27377d087da8615a3416d8ad523d5960b78dbeadd02a/opentelemetry_semantic_conventions-0.61b0-py3-none-any.whl", hash = "sha256:fa530a96be229795f8cef353739b618148b0fe2b4b3f005e60e262926c4d38e2", size = 231621, upload-time = "2026-03-04T14:17:19.33Z" }, + { url = "https://files.pythonhosted.org/packages/bc/14/67f8aa798857f8cf686f515bf93d9bb877ce952ddc8efae0fa25b45ce0d6/opentelemetry_semantic_conventions-0.66b1-py3-none-any.whl", hash = "sha256:d4cddeb4315490b35213f55e2bdc9ac54bb1e4d318927475bed62b35545e581b", size = 206279, upload-time = "2026-10-06T17:32:56.103Z" }, ] [[package]] @@ -816,7 +834,7 @@ wheels = [ [[package]] name = "posthog" -version = "7.75.4" +version = "7.75.11" source = { directory = "../../" } dependencies = [ { name = "backoff" }, @@ -1712,15 +1730,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7b/d9/8d95e906764a386a3d3b596f3c68bb63687dfca806373509f51ce8eea81f/xxhash-3.6.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:15e0dac10eb9309508bfc41f7f9deaa7755c69e35af835db9cb10751adebc35d", size = 31565, upload-time = "2025-10-02T14:37:06.966Z" }, ] -[[package]] -name = "zipp" -version = "3.23.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166", size = 25547, upload-time = "2025-06-08T17:06:39.4Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276, upload-time = "2025-06-08T17:06:38.034Z" }, -] - [[package]] name = "zstandard" version = "0.25.0" diff --git a/examples/example-ai-llamaindex/uv.lock b/examples/example-ai-llamaindex/uv.lock index f74f56fb..af7f08f0 100644 --- a/examples/example-ai-llamaindex/uv.lock +++ b/examples/example-ai-llamaindex/uv.lock @@ -803,18 +803,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/58/a2/bb081bab032533a855d44de1d56f8e8426114ff1ba5d1f07a438a0a654f8/idna-3.20-py3-none-any.whl", hash = "sha256:ab7ae7122974553370f0bdb919e1a960b2cd1bc1ef0276416d896db81c14582c", size = 69583, upload-time = "2026-09-17T14:11:03.168Z" }, ] -[[package]] -name = "importlib-metadata" -version = "8.7.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "zipp" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/f3/49/3b30cad09e7771a4982d9975a8cbf64f00d4a1ececb53297f1d9a7be1b10/importlib_metadata-8.7.1.tar.gz", hash = "sha256:49fef1ae6440c182052f407c8d34a68f72efc36db9ca90dc0113398f2fdde8bb", size = 57107, upload-time = "2025-12-21T10:00:19.278Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fa/5e/f8e9a1d23b9c20a551a8a02ea3637b4642e22c2626e3a13a9a29cdea99eb/importlib_metadata-8.7.1-py3-none-any.whl", hash = "sha256:5a1f80bf1daa489495071efbb095d75a634cf28a8bc299581244063b53176151", size = 27865, upload-time = "2025-12-21T10:00:18.329Z" }, -] - [[package]] name = "inflection" version = "0.5.1" @@ -1570,50 +1558,80 @@ wheels = [ [[package]] name = "opentelemetry-api" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "importlib-metadata" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2c/1d/4049a9e8698361cc1a1aa03a6c59e4fa4c71e0c0f94a30f988a6876a2ae6/opentelemetry_api-1.40.0.tar.gz", hash = "sha256:159be641c0b04d11e9ecd576906462773eb97ae1b657730f0ecf64d32071569f", size = 70851, upload-time = "2026-03-04T14:17:21.555Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2e/02/6e0ae9cc61bd3169d401077b507b3ebc344745171e1051ab430be012dcd9/opentelemetry_api-1.45.1.tar.gz", hash = "sha256:aa38ed19bcc084ba42782a73255b3582283eced7ad6dddbd6695189e69adfb75", size = 72804, upload-time = "2026-10-06T17:32:58.133Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/41/f7dcf80b81ee8e71c1a2b59f14208bc723edbd89ed027a73b175abf6348e/opentelemetry_api-1.45.1-py3-none-any.whl", hash = "sha256:b31553efa588ae44bc306f863c785c5333a9ecc091248c6ee68b4b6c87fdedfb", size = 60256, upload-time = "2026-10-06T17:32:33.506Z" }, +] + +[[package]] +name = "opentelemetry-exporter-http-transport" +version = "0.66b1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/62/0c/e3ebdb4b507f66afcc905e6885a4946969bd75b45988492643356fbbdc63/opentelemetry_exporter_http_transport-0.66b1.tar.gz", hash = "sha256:443080203bf52586ce0b2ad901e8951c61833eab1aa539ae6f1f16fe9e8e7952", size = 11693, upload-time = "2026-10-06T17:32:59.65Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/69/6af86ff66492b481c6a4c05dcfd68beb47ed8ba046440a26a2aac76b95c7/opentelemetry_exporter_http_transport-0.66b1-py3-none-any.whl", hash = "sha256:2f95404bdee7f9d2d529c7de56c7bd86d014d774d8fbf137810e0167f8a492bf", size = 12155, upload-time = "2026-10-06T17:32:35.454Z" }, +] + +[package.optional-dependencies] +requests = [ + { name = "requests" }, +] + +[[package]] +name = "opentelemetry-exporter-otlp-common" +version = "0.66b1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-sdk" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cb/19/41de712173f43057e4532d42ece7d0c6d4210d353e5752433cb14987643f/opentelemetry_exporter_otlp_common-0.66b1.tar.gz", hash = "sha256:6b1403487a2185ac1feb45fd5546fdf8630ce71c36bcefaadf51e2130e9e23f9", size = 14325, upload-time = "2026-10-06T17:33:01.725Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5f/bf/93795954016c522008da367da292adceed71cca6ee1717e1d64c83089099/opentelemetry_api-1.40.0-py3-none-any.whl", hash = "sha256:82dd69331ae74b06f6a874704be0cfaa49a1650e1537d4a813b86ecef7d0ecf9", size = 68676, upload-time = "2026-03-04T14:17:01.24Z" }, + { url = "https://files.pythonhosted.org/packages/fc/39/8c23d67665c762aa51840fa06f86e902e8f6f1693bc8d7e3d98cd6e2f753/opentelemetry_exporter_otlp_common-0.66b1-py3-none-any.whl", hash = "sha256:00ff8592c3a7cb729ff3fdc7ffa12372c243bdf2163e80c180994d0c7bd83ee9", size = 12385, upload-time = "2026-10-06T17:32:38.177Z" }, ] [[package]] name = "opentelemetry-exporter-otlp-proto-common" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-proto" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/51/bc/1559d46557fe6eca0b46c88d4c2676285f1f3be2e8d06bb5d15fbffc814a/opentelemetry_exporter_otlp_proto_common-1.40.0.tar.gz", hash = "sha256:1cbee86a4064790b362a86601ee7934f368b81cd4cc2f2e163902a6e7818a0fa", size = 20416, upload-time = "2026-03-04T14:17:23.801Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c1/8e/65e85e5137991a3c493b11682151d198638a5bc1dd4b4c5f67e013c57d7c/opentelemetry_exporter_otlp_proto_common-1.45.1.tar.gz", hash = "sha256:2e4adcc3a67bcf57804fc49514f0ef64974ca7590aa3491da389852b4a0628f6", size = 18873, upload-time = "2026-10-06T17:33:04.471Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8b/ca/8f122055c97a932311a3f640273f084e738008933503d0c2563cd5d591fc/opentelemetry_exporter_otlp_proto_common-1.40.0-py3-none-any.whl", hash = "sha256:7081ff453835a82417bf38dccf122c827c3cbc94f2079b03bba02a3165f25149", size = 18369, upload-time = "2026-03-04T14:17:04.796Z" }, + { url = "https://files.pythonhosted.org/packages/84/aa/92f225d353904e7f70b8b3e3c1b02db0cf56f744c2e83c581dc372e78873/opentelemetry_exporter_otlp_proto_common-1.45.1-py3-none-any.whl", hash = "sha256:2f446183ae7047b036226f1d846c41a834b0e8755ad13b51a51dd38952eb466c", size = 15393, upload-time = "2026-10-06T17:32:41.911Z" }, ] [[package]] name = "opentelemetry-exporter-otlp-proto-http" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "googleapis-common-protos" }, { name = "opentelemetry-api" }, + { name = "opentelemetry-exporter-http-transport", extra = ["requests"] }, + { name = "opentelemetry-exporter-otlp-common" }, { name = "opentelemetry-exporter-otlp-proto-common" }, { name = "opentelemetry-proto" }, { name = "opentelemetry-sdk" }, { name = "requests" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2e/fa/73d50e2c15c56be4d000c98e24221d494674b0cc95524e2a8cb3856d95a4/opentelemetry_exporter_otlp_proto_http-1.40.0.tar.gz", hash = "sha256:db48f5e0f33217588bbc00274a31517ba830da576e59503507c839b38fa0869c", size = 17772, upload-time = "2026-03-04T14:17:25.324Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/17/26487707ea4caa97b17e6e4b5fa72133a53512ffa2f5cf7a49ef284b29cb/opentelemetry_exporter_otlp_proto_http-1.45.1.tar.gz", hash = "sha256:45c218405ce3fd879596924b1874bf9a8f6880206d61065c5a912c8e5c297fb7", size = 28839, upload-time = "2026-10-06T17:33:05.713Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/3a/8865d6754e61c9fb170cdd530a124a53769ee5f740236064816eb0ca7301/opentelemetry_exporter_otlp_proto_http-1.40.0-py3-none-any.whl", hash = "sha256:a8d1dab28f504c5d96577d6509f80a8150e44e8f45f82cdbe0e34c99ab040069", size = 19960, upload-time = "2026-03-04T14:17:07.153Z" }, + { url = "https://files.pythonhosted.org/packages/aa/1f/517eaa0187ba106a9da97160ce2add3a371812681dc440930b267f714e42/opentelemetry_exporter_otlp_proto_http-1.45.1-py3-none-any.whl", hash = "sha256:24a97cf3753c7fb52fad44a696e452ff371686339e2acf3309e2eda3d0230700", size = 22180, upload-time = "2026-10-06T17:32:43.946Z" }, ] [[package]] name = "opentelemetry-instrumentation" -version = "0.61b0" +version = "0.66b1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, @@ -1621,9 +1639,9 @@ dependencies = [ { name = "packaging" }, { name = "wrapt" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/da/37/6bf8e66bfcee5d3c6515b79cb2ee9ad05fe573c20f7ceb288d0e7eeec28c/opentelemetry_instrumentation-0.61b0.tar.gz", hash = "sha256:cb21b48db738c9de196eba6b805b4ff9de3b7f187e4bbf9a466fa170514f1fc7", size = 32606, upload-time = "2026-03-04T14:20:16.825Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a5/03/89e47ff8d52a4f83b343e6eb9ef1698ff45357216e5b6b2b21e0da5c5c7d/opentelemetry_instrumentation-0.66b1.tar.gz", hash = "sha256:e79a510f7d87c72d95e964ddb42193a0d9a75668c027d980eab032ea1322a5ce", size = 43308, upload-time = "2026-10-06T17:36:10.703Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d8/3e/f6f10f178b6316de67f0dfdbbb699a24fbe8917cf1743c1595fb9dcdd461/opentelemetry_instrumentation-0.61b0-py3-none-any.whl", hash = "sha256:92a93a280e69788e8f88391247cc530fd81f16f2b011979d4d6398f805cfbc63", size = 33448, upload-time = "2026-03-04T14:19:02.447Z" }, + { url = "https://files.pythonhosted.org/packages/da/b2/d1413681ff43e13ac9860df27e1226d3199ab0b97b352ceea41abcc660a5/opentelemetry_instrumentation-0.66b1-py3-none-any.whl", hash = "sha256:4c4aa14dc9a24a02325a9d4c42c4d0208dbb1374c2b1b8fe6c9392d59f3e1008", size = 36904, upload-time = "2026-10-06T17:35:11.663Z" }, ] [[package]] @@ -1644,41 +1662,41 @@ wheels = [ [[package]] name = "opentelemetry-proto" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "protobuf" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4c/77/dd38991db037fdfce45849491cb61de5ab000f49824a00230afb112a4392/opentelemetry_proto-1.40.0.tar.gz", hash = "sha256:03f639ca129ba513f5819810f5b1f42bcb371391405d99c168fe6937c62febcd", size = 45667, upload-time = "2026-03-04T14:17:31.194Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4b/7f/15f014fb195da6c2dbb6c71399b8e76824878718e94de6454038488eed28/opentelemetry_proto-1.45.1.tar.gz", hash = "sha256:79e0fb95e4616691a469439238aa9224d75779b3e108e895d1aa125ab29ca77c", size = 46488, upload-time = "2026-10-06T17:33:11.49Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b9/b2/189b2577dde745b15625b3214302605b1353436219d42b7912e77fa8dc24/opentelemetry_proto-1.40.0-py3-none-any.whl", hash = "sha256:266c4385d88923a23d63e353e9761af0f47a6ed0d486979777fe4de59dc9b25f", size = 72073, upload-time = "2026-03-04T14:17:16.673Z" }, + { url = "https://files.pythonhosted.org/packages/ab/9a/42ec8180a769516ae757e893b69736826efceac7332553915b4528a91c6d/opentelemetry_proto-1.45.1-py3-none-any.whl", hash = "sha256:f38e2a8413053c180cd3d2637fbb279673ec2f6a6e09c995aafa2f452c52b46e", size = 72488, upload-time = "2026-10-06T17:32:53.057Z" }, ] [[package]] name = "opentelemetry-sdk" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, { name = "opentelemetry-semantic-conventions" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/58/fd/3c3125b20ba18ce2155ba9ea74acb0ae5d25f8cd39cfd37455601b7955cc/opentelemetry_sdk-1.40.0.tar.gz", hash = "sha256:18e9f5ec20d859d268c7cb3c5198c8d105d073714db3de50b593b8c1345a48f2", size = 184252, upload-time = "2026-03-04T14:17:31.87Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/79/7392e21a1c8f0c61d90b223e31c7e48cb9d452e91a6b820ad24cca5f23c4/opentelemetry_sdk-1.45.1.tar.gz", hash = "sha256:63d24a6ca645019a631e6a51999c73e93adcac1196ca640b8ae78a7cc4762bf3", size = 218324, upload-time = "2026-10-06T17:33:13.26Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/c5/6a852903d8bfac758c6dc6e9a68b015d3c33f2f1be5e9591e0f4b69c7e0a/opentelemetry_sdk-1.40.0-py3-none-any.whl", hash = "sha256:787d2154a71f4b3d81f20524a8ce061b7db667d24e46753f32a7bc48f1c1f3f1", size = 141951, upload-time = "2026-03-04T14:17:17.961Z" }, + { url = "https://files.pythonhosted.org/packages/95/3c/87c42b4bd6dd297536f04cd9383d212ac557ecd49f2cbdcd46da1c9ef5c8/opentelemetry_sdk-1.45.1-py3-none-any.whl", hash = "sha256:c604c11dc429810812348989115fa44bd558772a3d7442afc43d024f2c250ca4", size = 140063, upload-time = "2026-10-06T17:32:55.04Z" }, ] [[package]] name = "opentelemetry-semantic-conventions" -version = "0.61b0" +version = "0.66b1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6d/c0/4ae7973f3c2cfd2b6e321f1675626f0dab0a97027cc7a297474c9c8f3d04/opentelemetry_semantic_conventions-0.61b0.tar.gz", hash = "sha256:072f65473c5d7c6dc0355b27d6c9d1a679d63b6d4b4b16a9773062cb7e31192a", size = 145755, upload-time = "2026-03-04T14:17:32.664Z" } +sdist = { url = "https://files.pythonhosted.org/packages/46/e4/dbbfb2a010c4db2224a5114638acede6fe563d33cc20fb1752cebcbe6298/opentelemetry_semantic_conventions-0.66b1.tar.gz", hash = "sha256:497ca63bf383723411e8eaf60c8779e9877633c936bb641080adab59d0eb6ec8", size = 150250, upload-time = "2026-10-06T17:33:14.073Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b2/37/cc6a55e448deaa9b27377d087da8615a3416d8ad523d5960b78dbeadd02a/opentelemetry_semantic_conventions-0.61b0-py3-none-any.whl", hash = "sha256:fa530a96be229795f8cef353739b618148b0fe2b4b3f005e60e262926c4d38e2", size = 231621, upload-time = "2026-03-04T14:17:19.33Z" }, + { url = "https://files.pythonhosted.org/packages/bc/14/67f8aa798857f8cf686f515bf93d9bb877ce952ddc8efae0fa25b45ce0d6/opentelemetry_semantic_conventions-0.66b1-py3-none-any.whl", hash = "sha256:d4cddeb4315490b35213f55e2bdc9ac54bb1e4d318927475bed62b35545e581b", size = 206279, upload-time = "2026-10-06T17:32:56.103Z" }, ] [[package]] @@ -1808,7 +1826,7 @@ wheels = [ [[package]] name = "posthog" -version = "7.75.4" +version = "7.75.11" source = { directory = "../../" } dependencies = [ { name = "backoff" }, @@ -2788,12 +2806,3 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/88/91/41e284ca2cf5211e05dae031d126a3668aea88fa759df56e7e35c6ad25ba/yarl-1.25.1-cp315-cp315t-win_arm64.whl", hash = "sha256:783dd1467083f4d3f7722ad6a313f24c173e7571372738fcb7a6e6d1ba48df25", size = 101804, upload-time = "2026-09-15T19:34:57.231Z" }, { url = "https://files.pythonhosted.org/packages/54/22/318c7980066769c6bcd9221ed2248294f5698811da099013098c670565ed/yarl-1.25.1-py3-none-any.whl", hash = "sha256:681c758b0490f9e96b78e5fa8e8dc6e648e9185bb6eaebe73183c33ea0c445f3", size = 63617, upload-time = "2026-09-15T19:34:59.616Z" }, ] - -[[package]] -name = "zipp" -version = "3.23.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166", size = 25547, upload-time = "2025-06-08T17:06:39.4Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276, upload-time = "2025-06-08T17:06:38.034Z" }, -] diff --git a/examples/example-ai-mirascope/uv.lock b/examples/example-ai-mirascope/uv.lock index 236ddaeb..57bf711c 100644 --- a/examples/example-ai-mirascope/uv.lock +++ b/examples/example-ai-mirascope/uv.lock @@ -273,18 +273,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/57/b0/0e52c878c53f245edd3a11020f20979b3f490f245af532c7cae3027754b5/idna-3.19-py3-none-any.whl", hash = "sha256:815e7be7a7806d54abb586dc943addc79e8b2ee16915059658cbeff4b1b43bf4", size = 68550, upload-time = "2026-08-18T05:14:22.343Z" }, ] -[[package]] -name = "importlib-metadata" -version = "8.7.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "zipp" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/f3/49/3b30cad09e7771a4982d9975a8cbf64f00d4a1ececb53297f1d9a7be1b10/importlib_metadata-8.7.1.tar.gz", hash = "sha256:49fef1ae6440c182052f407c8d34a68f72efc36db9ca90dc0113398f2fdde8bb", size = 57107, upload-time = "2025-12-21T10:00:19.278Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fa/5e/f8e9a1d23b9c20a551a8a02ea3637b4642e22c2626e3a13a9a29cdea99eb/importlib_metadata-8.7.1-py3-none-any.whl", hash = "sha256:5a1f80bf1daa489495071efbb095d75a634cf28a8bc299581244063b53176151", size = 27865, upload-time = "2025-12-21T10:00:18.329Z" }, -] - [[package]] name = "jiter" version = "0.13.0" @@ -419,50 +407,80 @@ wheels = [ [[package]] name = "opentelemetry-api" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "importlib-metadata" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2c/1d/4049a9e8698361cc1a1aa03a6c59e4fa4c71e0c0f94a30f988a6876a2ae6/opentelemetry_api-1.40.0.tar.gz", hash = "sha256:159be641c0b04d11e9ecd576906462773eb97ae1b657730f0ecf64d32071569f", size = 70851, upload-time = "2026-03-04T14:17:21.555Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2e/02/6e0ae9cc61bd3169d401077b507b3ebc344745171e1051ab430be012dcd9/opentelemetry_api-1.45.1.tar.gz", hash = "sha256:aa38ed19bcc084ba42782a73255b3582283eced7ad6dddbd6695189e69adfb75", size = 72804, upload-time = "2026-10-06T17:32:58.133Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/41/f7dcf80b81ee8e71c1a2b59f14208bc723edbd89ed027a73b175abf6348e/opentelemetry_api-1.45.1-py3-none-any.whl", hash = "sha256:b31553efa588ae44bc306f863c785c5333a9ecc091248c6ee68b4b6c87fdedfb", size = 60256, upload-time = "2026-10-06T17:32:33.506Z" }, +] + +[[package]] +name = "opentelemetry-exporter-http-transport" +version = "0.66b1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/62/0c/e3ebdb4b507f66afcc905e6885a4946969bd75b45988492643356fbbdc63/opentelemetry_exporter_http_transport-0.66b1.tar.gz", hash = "sha256:443080203bf52586ce0b2ad901e8951c61833eab1aa539ae6f1f16fe9e8e7952", size = 11693, upload-time = "2026-10-06T17:32:59.65Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/69/6af86ff66492b481c6a4c05dcfd68beb47ed8ba046440a26a2aac76b95c7/opentelemetry_exporter_http_transport-0.66b1-py3-none-any.whl", hash = "sha256:2f95404bdee7f9d2d529c7de56c7bd86d014d774d8fbf137810e0167f8a492bf", size = 12155, upload-time = "2026-10-06T17:32:35.454Z" }, +] + +[package.optional-dependencies] +requests = [ + { name = "requests" }, +] + +[[package]] +name = "opentelemetry-exporter-otlp-common" +version = "0.66b1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-sdk" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cb/19/41de712173f43057e4532d42ece7d0c6d4210d353e5752433cb14987643f/opentelemetry_exporter_otlp_common-0.66b1.tar.gz", hash = "sha256:6b1403487a2185ac1feb45fd5546fdf8630ce71c36bcefaadf51e2130e9e23f9", size = 14325, upload-time = "2026-10-06T17:33:01.725Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5f/bf/93795954016c522008da367da292adceed71cca6ee1717e1d64c83089099/opentelemetry_api-1.40.0-py3-none-any.whl", hash = "sha256:82dd69331ae74b06f6a874704be0cfaa49a1650e1537d4a813b86ecef7d0ecf9", size = 68676, upload-time = "2026-03-04T14:17:01.24Z" }, + { url = "https://files.pythonhosted.org/packages/fc/39/8c23d67665c762aa51840fa06f86e902e8f6f1693bc8d7e3d98cd6e2f753/opentelemetry_exporter_otlp_common-0.66b1-py3-none-any.whl", hash = "sha256:00ff8592c3a7cb729ff3fdc7ffa12372c243bdf2163e80c180994d0c7bd83ee9", size = 12385, upload-time = "2026-10-06T17:32:38.177Z" }, ] [[package]] name = "opentelemetry-exporter-otlp-proto-common" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-proto" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/51/bc/1559d46557fe6eca0b46c88d4c2676285f1f3be2e8d06bb5d15fbffc814a/opentelemetry_exporter_otlp_proto_common-1.40.0.tar.gz", hash = "sha256:1cbee86a4064790b362a86601ee7934f368b81cd4cc2f2e163902a6e7818a0fa", size = 20416, upload-time = "2026-03-04T14:17:23.801Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c1/8e/65e85e5137991a3c493b11682151d198638a5bc1dd4b4c5f67e013c57d7c/opentelemetry_exporter_otlp_proto_common-1.45.1.tar.gz", hash = "sha256:2e4adcc3a67bcf57804fc49514f0ef64974ca7590aa3491da389852b4a0628f6", size = 18873, upload-time = "2026-10-06T17:33:04.471Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8b/ca/8f122055c97a932311a3f640273f084e738008933503d0c2563cd5d591fc/opentelemetry_exporter_otlp_proto_common-1.40.0-py3-none-any.whl", hash = "sha256:7081ff453835a82417bf38dccf122c827c3cbc94f2079b03bba02a3165f25149", size = 18369, upload-time = "2026-03-04T14:17:04.796Z" }, + { url = "https://files.pythonhosted.org/packages/84/aa/92f225d353904e7f70b8b3e3c1b02db0cf56f744c2e83c581dc372e78873/opentelemetry_exporter_otlp_proto_common-1.45.1-py3-none-any.whl", hash = "sha256:2f446183ae7047b036226f1d846c41a834b0e8755ad13b51a51dd38952eb466c", size = 15393, upload-time = "2026-10-06T17:32:41.911Z" }, ] [[package]] name = "opentelemetry-exporter-otlp-proto-http" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "googleapis-common-protos" }, { name = "opentelemetry-api" }, + { name = "opentelemetry-exporter-http-transport", extra = ["requests"] }, + { name = "opentelemetry-exporter-otlp-common" }, { name = "opentelemetry-exporter-otlp-proto-common" }, { name = "opentelemetry-proto" }, { name = "opentelemetry-sdk" }, { name = "requests" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2e/fa/73d50e2c15c56be4d000c98e24221d494674b0cc95524e2a8cb3856d95a4/opentelemetry_exporter_otlp_proto_http-1.40.0.tar.gz", hash = "sha256:db48f5e0f33217588bbc00274a31517ba830da576e59503507c839b38fa0869c", size = 17772, upload-time = "2026-03-04T14:17:25.324Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/17/26487707ea4caa97b17e6e4b5fa72133a53512ffa2f5cf7a49ef284b29cb/opentelemetry_exporter_otlp_proto_http-1.45.1.tar.gz", hash = "sha256:45c218405ce3fd879596924b1874bf9a8f6880206d61065c5a912c8e5c297fb7", size = 28839, upload-time = "2026-10-06T17:33:05.713Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/3a/8865d6754e61c9fb170cdd530a124a53769ee5f740236064816eb0ca7301/opentelemetry_exporter_otlp_proto_http-1.40.0-py3-none-any.whl", hash = "sha256:a8d1dab28f504c5d96577d6509f80a8150e44e8f45f82cdbe0e34c99ab040069", size = 19960, upload-time = "2026-03-04T14:17:07.153Z" }, + { url = "https://files.pythonhosted.org/packages/aa/1f/517eaa0187ba106a9da97160ce2add3a371812681dc440930b267f714e42/opentelemetry_exporter_otlp_proto_http-1.45.1-py3-none-any.whl", hash = "sha256:24a97cf3753c7fb52fad44a696e452ff371686339e2acf3309e2eda3d0230700", size = 22180, upload-time = "2026-10-06T17:32:43.946Z" }, ] [[package]] name = "opentelemetry-instrumentation" -version = "0.61b0" +version = "0.66b1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, @@ -470,9 +488,9 @@ dependencies = [ { name = "packaging" }, { name = "wrapt" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/da/37/6bf8e66bfcee5d3c6515b79cb2ee9ad05fe573c20f7ceb288d0e7eeec28c/opentelemetry_instrumentation-0.61b0.tar.gz", hash = "sha256:cb21b48db738c9de196eba6b805b4ff9de3b7f187e4bbf9a466fa170514f1fc7", size = 32606, upload-time = "2026-03-04T14:20:16.825Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a5/03/89e47ff8d52a4f83b343e6eb9ef1698ff45357216e5b6b2b21e0da5c5c7d/opentelemetry_instrumentation-0.66b1.tar.gz", hash = "sha256:e79a510f7d87c72d95e964ddb42193a0d9a75668c027d980eab032ea1322a5ce", size = 43308, upload-time = "2026-10-06T17:36:10.703Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d8/3e/f6f10f178b6316de67f0dfdbbb699a24fbe8917cf1743c1595fb9dcdd461/opentelemetry_instrumentation-0.61b0-py3-none-any.whl", hash = "sha256:92a93a280e69788e8f88391247cc530fd81f16f2b011979d4d6398f805cfbc63", size = 33448, upload-time = "2026-03-04T14:19:02.447Z" }, + { url = "https://files.pythonhosted.org/packages/da/b2/d1413681ff43e13ac9860df27e1226d3199ab0b97b352ceea41abcc660a5/opentelemetry_instrumentation-0.66b1-py3-none-any.whl", hash = "sha256:4c4aa14dc9a24a02325a9d4c42c4d0208dbb1374c2b1b8fe6c9392d59f3e1008", size = 36904, upload-time = "2026-10-06T17:35:11.663Z" }, ] [[package]] @@ -491,41 +509,41 @@ wheels = [ [[package]] name = "opentelemetry-proto" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "protobuf" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4c/77/dd38991db037fdfce45849491cb61de5ab000f49824a00230afb112a4392/opentelemetry_proto-1.40.0.tar.gz", hash = "sha256:03f639ca129ba513f5819810f5b1f42bcb371391405d99c168fe6937c62febcd", size = 45667, upload-time = "2026-03-04T14:17:31.194Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4b/7f/15f014fb195da6c2dbb6c71399b8e76824878718e94de6454038488eed28/opentelemetry_proto-1.45.1.tar.gz", hash = "sha256:79e0fb95e4616691a469439238aa9224d75779b3e108e895d1aa125ab29ca77c", size = 46488, upload-time = "2026-10-06T17:33:11.49Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b9/b2/189b2577dde745b15625b3214302605b1353436219d42b7912e77fa8dc24/opentelemetry_proto-1.40.0-py3-none-any.whl", hash = "sha256:266c4385d88923a23d63e353e9761af0f47a6ed0d486979777fe4de59dc9b25f", size = 72073, upload-time = "2026-03-04T14:17:16.673Z" }, + { url = "https://files.pythonhosted.org/packages/ab/9a/42ec8180a769516ae757e893b69736826efceac7332553915b4528a91c6d/opentelemetry_proto-1.45.1-py3-none-any.whl", hash = "sha256:f38e2a8413053c180cd3d2637fbb279673ec2f6a6e09c995aafa2f452c52b46e", size = 72488, upload-time = "2026-10-06T17:32:53.057Z" }, ] [[package]] name = "opentelemetry-sdk" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, { name = "opentelemetry-semantic-conventions" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/58/fd/3c3125b20ba18ce2155ba9ea74acb0ae5d25f8cd39cfd37455601b7955cc/opentelemetry_sdk-1.40.0.tar.gz", hash = "sha256:18e9f5ec20d859d268c7cb3c5198c8d105d073714db3de50b593b8c1345a48f2", size = 184252, upload-time = "2026-03-04T14:17:31.87Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/79/7392e21a1c8f0c61d90b223e31c7e48cb9d452e91a6b820ad24cca5f23c4/opentelemetry_sdk-1.45.1.tar.gz", hash = "sha256:63d24a6ca645019a631e6a51999c73e93adcac1196ca640b8ae78a7cc4762bf3", size = 218324, upload-time = "2026-10-06T17:33:13.26Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/c5/6a852903d8bfac758c6dc6e9a68b015d3c33f2f1be5e9591e0f4b69c7e0a/opentelemetry_sdk-1.40.0-py3-none-any.whl", hash = "sha256:787d2154a71f4b3d81f20524a8ce061b7db667d24e46753f32a7bc48f1c1f3f1", size = 141951, upload-time = "2026-03-04T14:17:17.961Z" }, + { url = "https://files.pythonhosted.org/packages/95/3c/87c42b4bd6dd297536f04cd9383d212ac557ecd49f2cbdcd46da1c9ef5c8/opentelemetry_sdk-1.45.1-py3-none-any.whl", hash = "sha256:c604c11dc429810812348989115fa44bd558772a3d7442afc43d024f2c250ca4", size = 140063, upload-time = "2026-10-06T17:32:55.04Z" }, ] [[package]] name = "opentelemetry-semantic-conventions" -version = "0.61b0" +version = "0.66b1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6d/c0/4ae7973f3c2cfd2b6e321f1675626f0dab0a97027cc7a297474c9c8f3d04/opentelemetry_semantic_conventions-0.61b0.tar.gz", hash = "sha256:072f65473c5d7c6dc0355b27d6c9d1a679d63b6d4b4b16a9773062cb7e31192a", size = 145755, upload-time = "2026-03-04T14:17:32.664Z" } +sdist = { url = "https://files.pythonhosted.org/packages/46/e4/dbbfb2a010c4db2224a5114638acede6fe563d33cc20fb1752cebcbe6298/opentelemetry_semantic_conventions-0.66b1.tar.gz", hash = "sha256:497ca63bf383723411e8eaf60c8779e9877633c936bb641080adab59d0eb6ec8", size = 150250, upload-time = "2026-10-06T17:33:14.073Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b2/37/cc6a55e448deaa9b27377d087da8615a3416d8ad523d5960b78dbeadd02a/opentelemetry_semantic_conventions-0.61b0-py3-none-any.whl", hash = "sha256:fa530a96be229795f8cef353739b618148b0fe2b4b3f005e60e262926c4d38e2", size = 231621, upload-time = "2026-03-04T14:17:19.33Z" }, + { url = "https://files.pythonhosted.org/packages/bc/14/67f8aa798857f8cf686f515bf93d9bb877ce952ddc8efae0fa25b45ce0d6/opentelemetry_semantic_conventions-0.66b1-py3-none-any.whl", hash = "sha256:d4cddeb4315490b35213f55e2bdc9ac54bb1e4d318927475bed62b35545e581b", size = 206279, upload-time = "2026-10-06T17:32:56.103Z" }, ] [[package]] @@ -539,7 +557,7 @@ wheels = [ [[package]] name = "posthog" -version = "7.75.4" +version = "7.75.11" source = { directory = "../../" } dependencies = [ { name = "backoff" }, @@ -904,12 +922,3 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/46/78/10ad9781128ed2f99dbc474f43283b13fea8ba58723e98844367531c18e9/wrapt-1.17.3-cp314-cp314t-win_arm64.whl", hash = "sha256:f38e60678850c42461d4202739f9bf1e3a737c7ad283638251e79cc49effb6b6", size = 38471, upload-time = "2025-08-12T05:52:57.784Z" }, { url = "https://files.pythonhosted.org/packages/1f/f6/a933bd70f98e9cf3e08167fc5cd7aaaca49147e48411c0bd5ae701bb2194/wrapt-1.17.3-py3-none-any.whl", hash = "sha256:7171ae35d2c33d326ac19dd8facb1e82e5fd04ef8c6c0e394d7af55a55051c22", size = 23591, upload-time = "2025-08-12T05:53:20.674Z" }, ] - -[[package]] -name = "zipp" -version = "3.23.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166", size = 25547, upload-time = "2025-06-08T17:06:39.4Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276, upload-time = "2025-06-08T17:06:38.034Z" }, -] diff --git a/examples/example-ai-mistral/uv.lock b/examples/example-ai-mistral/uv.lock index 3ae20625..af761686 100644 --- a/examples/example-ai-mistral/uv.lock +++ b/examples/example-ai-mistral/uv.lock @@ -262,18 +262,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/57/b0/0e52c878c53f245edd3a11020f20979b3f490f245af532c7cae3027754b5/idna-3.19-py3-none-any.whl", hash = "sha256:815e7be7a7806d54abb586dc943addc79e8b2ee16915059658cbeff4b1b43bf4", size = 68550, upload-time = "2026-08-18T05:14:22.343Z" }, ] -[[package]] -name = "importlib-metadata" -version = "8.7.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "zipp" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/f3/49/3b30cad09e7771a4982d9975a8cbf64f00d4a1ececb53297f1d9a7be1b10/importlib_metadata-8.7.1.tar.gz", hash = "sha256:49fef1ae6440c182052f407c8d34a68f72efc36db9ca90dc0113398f2fdde8bb", size = 57107, upload-time = "2025-12-21T10:00:19.278Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fa/5e/f8e9a1d23b9c20a551a8a02ea3637b4642e22c2626e3a13a9a29cdea99eb/importlib_metadata-8.7.1-py3-none-any.whl", hash = "sha256:5a1f80bf1daa489495071efbb095d75a634cf28a8bc299581244063b53176151", size = 27865, upload-time = "2025-12-21T10:00:18.329Z" }, -] - [[package]] name = "jiter" version = "0.13.0" @@ -392,50 +380,80 @@ wheels = [ [[package]] name = "opentelemetry-api" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "importlib-metadata" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2c/1d/4049a9e8698361cc1a1aa03a6c59e4fa4c71e0c0f94a30f988a6876a2ae6/opentelemetry_api-1.40.0.tar.gz", hash = "sha256:159be641c0b04d11e9ecd576906462773eb97ae1b657730f0ecf64d32071569f", size = 70851, upload-time = "2026-03-04T14:17:21.555Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2e/02/6e0ae9cc61bd3169d401077b507b3ebc344745171e1051ab430be012dcd9/opentelemetry_api-1.45.1.tar.gz", hash = "sha256:aa38ed19bcc084ba42782a73255b3582283eced7ad6dddbd6695189e69adfb75", size = 72804, upload-time = "2026-10-06T17:32:58.133Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/41/f7dcf80b81ee8e71c1a2b59f14208bc723edbd89ed027a73b175abf6348e/opentelemetry_api-1.45.1-py3-none-any.whl", hash = "sha256:b31553efa588ae44bc306f863c785c5333a9ecc091248c6ee68b4b6c87fdedfb", size = 60256, upload-time = "2026-10-06T17:32:33.506Z" }, +] + +[[package]] +name = "opentelemetry-exporter-http-transport" +version = "0.66b1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/62/0c/e3ebdb4b507f66afcc905e6885a4946969bd75b45988492643356fbbdc63/opentelemetry_exporter_http_transport-0.66b1.tar.gz", hash = "sha256:443080203bf52586ce0b2ad901e8951c61833eab1aa539ae6f1f16fe9e8e7952", size = 11693, upload-time = "2026-10-06T17:32:59.65Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/69/6af86ff66492b481c6a4c05dcfd68beb47ed8ba046440a26a2aac76b95c7/opentelemetry_exporter_http_transport-0.66b1-py3-none-any.whl", hash = "sha256:2f95404bdee7f9d2d529c7de56c7bd86d014d774d8fbf137810e0167f8a492bf", size = 12155, upload-time = "2026-10-06T17:32:35.454Z" }, +] + +[package.optional-dependencies] +requests = [ + { name = "requests" }, +] + +[[package]] +name = "opentelemetry-exporter-otlp-common" +version = "0.66b1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-sdk" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cb/19/41de712173f43057e4532d42ece7d0c6d4210d353e5752433cb14987643f/opentelemetry_exporter_otlp_common-0.66b1.tar.gz", hash = "sha256:6b1403487a2185ac1feb45fd5546fdf8630ce71c36bcefaadf51e2130e9e23f9", size = 14325, upload-time = "2026-10-06T17:33:01.725Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5f/bf/93795954016c522008da367da292adceed71cca6ee1717e1d64c83089099/opentelemetry_api-1.40.0-py3-none-any.whl", hash = "sha256:82dd69331ae74b06f6a874704be0cfaa49a1650e1537d4a813b86ecef7d0ecf9", size = 68676, upload-time = "2026-03-04T14:17:01.24Z" }, + { url = "https://files.pythonhosted.org/packages/fc/39/8c23d67665c762aa51840fa06f86e902e8f6f1693bc8d7e3d98cd6e2f753/opentelemetry_exporter_otlp_common-0.66b1-py3-none-any.whl", hash = "sha256:00ff8592c3a7cb729ff3fdc7ffa12372c243bdf2163e80c180994d0c7bd83ee9", size = 12385, upload-time = "2026-10-06T17:32:38.177Z" }, ] [[package]] name = "opentelemetry-exporter-otlp-proto-common" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-proto" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/51/bc/1559d46557fe6eca0b46c88d4c2676285f1f3be2e8d06bb5d15fbffc814a/opentelemetry_exporter_otlp_proto_common-1.40.0.tar.gz", hash = "sha256:1cbee86a4064790b362a86601ee7934f368b81cd4cc2f2e163902a6e7818a0fa", size = 20416, upload-time = "2026-03-04T14:17:23.801Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c1/8e/65e85e5137991a3c493b11682151d198638a5bc1dd4b4c5f67e013c57d7c/opentelemetry_exporter_otlp_proto_common-1.45.1.tar.gz", hash = "sha256:2e4adcc3a67bcf57804fc49514f0ef64974ca7590aa3491da389852b4a0628f6", size = 18873, upload-time = "2026-10-06T17:33:04.471Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8b/ca/8f122055c97a932311a3f640273f084e738008933503d0c2563cd5d591fc/opentelemetry_exporter_otlp_proto_common-1.40.0-py3-none-any.whl", hash = "sha256:7081ff453835a82417bf38dccf122c827c3cbc94f2079b03bba02a3165f25149", size = 18369, upload-time = "2026-03-04T14:17:04.796Z" }, + { url = "https://files.pythonhosted.org/packages/84/aa/92f225d353904e7f70b8b3e3c1b02db0cf56f744c2e83c581dc372e78873/opentelemetry_exporter_otlp_proto_common-1.45.1-py3-none-any.whl", hash = "sha256:2f446183ae7047b036226f1d846c41a834b0e8755ad13b51a51dd38952eb466c", size = 15393, upload-time = "2026-10-06T17:32:41.911Z" }, ] [[package]] name = "opentelemetry-exporter-otlp-proto-http" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "googleapis-common-protos" }, { name = "opentelemetry-api" }, + { name = "opentelemetry-exporter-http-transport", extra = ["requests"] }, + { name = "opentelemetry-exporter-otlp-common" }, { name = "opentelemetry-exporter-otlp-proto-common" }, { name = "opentelemetry-proto" }, { name = "opentelemetry-sdk" }, { name = "requests" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2e/fa/73d50e2c15c56be4d000c98e24221d494674b0cc95524e2a8cb3856d95a4/opentelemetry_exporter_otlp_proto_http-1.40.0.tar.gz", hash = "sha256:db48f5e0f33217588bbc00274a31517ba830da576e59503507c839b38fa0869c", size = 17772, upload-time = "2026-03-04T14:17:25.324Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/17/26487707ea4caa97b17e6e4b5fa72133a53512ffa2f5cf7a49ef284b29cb/opentelemetry_exporter_otlp_proto_http-1.45.1.tar.gz", hash = "sha256:45c218405ce3fd879596924b1874bf9a8f6880206d61065c5a912c8e5c297fb7", size = 28839, upload-time = "2026-10-06T17:33:05.713Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/3a/8865d6754e61c9fb170cdd530a124a53769ee5f740236064816eb0ca7301/opentelemetry_exporter_otlp_proto_http-1.40.0-py3-none-any.whl", hash = "sha256:a8d1dab28f504c5d96577d6509f80a8150e44e8f45f82cdbe0e34c99ab040069", size = 19960, upload-time = "2026-03-04T14:17:07.153Z" }, + { url = "https://files.pythonhosted.org/packages/aa/1f/517eaa0187ba106a9da97160ce2add3a371812681dc440930b267f714e42/opentelemetry_exporter_otlp_proto_http-1.45.1-py3-none-any.whl", hash = "sha256:24a97cf3753c7fb52fad44a696e452ff371686339e2acf3309e2eda3d0230700", size = 22180, upload-time = "2026-10-06T17:32:43.946Z" }, ] [[package]] name = "opentelemetry-instrumentation" -version = "0.61b0" +version = "0.66b1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, @@ -443,9 +461,9 @@ dependencies = [ { name = "packaging" }, { name = "wrapt" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/da/37/6bf8e66bfcee5d3c6515b79cb2ee9ad05fe573c20f7ceb288d0e7eeec28c/opentelemetry_instrumentation-0.61b0.tar.gz", hash = "sha256:cb21b48db738c9de196eba6b805b4ff9de3b7f187e4bbf9a466fa170514f1fc7", size = 32606, upload-time = "2026-03-04T14:20:16.825Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a5/03/89e47ff8d52a4f83b343e6eb9ef1698ff45357216e5b6b2b21e0da5c5c7d/opentelemetry_instrumentation-0.66b1.tar.gz", hash = "sha256:e79a510f7d87c72d95e964ddb42193a0d9a75668c027d980eab032ea1322a5ce", size = 43308, upload-time = "2026-10-06T17:36:10.703Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d8/3e/f6f10f178b6316de67f0dfdbbb699a24fbe8917cf1743c1595fb9dcdd461/opentelemetry_instrumentation-0.61b0-py3-none-any.whl", hash = "sha256:92a93a280e69788e8f88391247cc530fd81f16f2b011979d4d6398f805cfbc63", size = 33448, upload-time = "2026-03-04T14:19:02.447Z" }, + { url = "https://files.pythonhosted.org/packages/da/b2/d1413681ff43e13ac9860df27e1226d3199ab0b97b352ceea41abcc660a5/opentelemetry_instrumentation-0.66b1-py3-none-any.whl", hash = "sha256:4c4aa14dc9a24a02325a9d4c42c4d0208dbb1374c2b1b8fe6c9392d59f3e1008", size = 36904, upload-time = "2026-10-06T17:35:11.663Z" }, ] [[package]] @@ -464,41 +482,41 @@ wheels = [ [[package]] name = "opentelemetry-proto" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "protobuf" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4c/77/dd38991db037fdfce45849491cb61de5ab000f49824a00230afb112a4392/opentelemetry_proto-1.40.0.tar.gz", hash = "sha256:03f639ca129ba513f5819810f5b1f42bcb371391405d99c168fe6937c62febcd", size = 45667, upload-time = "2026-03-04T14:17:31.194Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4b/7f/15f014fb195da6c2dbb6c71399b8e76824878718e94de6454038488eed28/opentelemetry_proto-1.45.1.tar.gz", hash = "sha256:79e0fb95e4616691a469439238aa9224d75779b3e108e895d1aa125ab29ca77c", size = 46488, upload-time = "2026-10-06T17:33:11.49Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b9/b2/189b2577dde745b15625b3214302605b1353436219d42b7912e77fa8dc24/opentelemetry_proto-1.40.0-py3-none-any.whl", hash = "sha256:266c4385d88923a23d63e353e9761af0f47a6ed0d486979777fe4de59dc9b25f", size = 72073, upload-time = "2026-03-04T14:17:16.673Z" }, + { url = "https://files.pythonhosted.org/packages/ab/9a/42ec8180a769516ae757e893b69736826efceac7332553915b4528a91c6d/opentelemetry_proto-1.45.1-py3-none-any.whl", hash = "sha256:f38e2a8413053c180cd3d2637fbb279673ec2f6a6e09c995aafa2f452c52b46e", size = 72488, upload-time = "2026-10-06T17:32:53.057Z" }, ] [[package]] name = "opentelemetry-sdk" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, { name = "opentelemetry-semantic-conventions" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/58/fd/3c3125b20ba18ce2155ba9ea74acb0ae5d25f8cd39cfd37455601b7955cc/opentelemetry_sdk-1.40.0.tar.gz", hash = "sha256:18e9f5ec20d859d268c7cb3c5198c8d105d073714db3de50b593b8c1345a48f2", size = 184252, upload-time = "2026-03-04T14:17:31.87Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/79/7392e21a1c8f0c61d90b223e31c7e48cb9d452e91a6b820ad24cca5f23c4/opentelemetry_sdk-1.45.1.tar.gz", hash = "sha256:63d24a6ca645019a631e6a51999c73e93adcac1196ca640b8ae78a7cc4762bf3", size = 218324, upload-time = "2026-10-06T17:33:13.26Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/c5/6a852903d8bfac758c6dc6e9a68b015d3c33f2f1be5e9591e0f4b69c7e0a/opentelemetry_sdk-1.40.0-py3-none-any.whl", hash = "sha256:787d2154a71f4b3d81f20524a8ce061b7db667d24e46753f32a7bc48f1c1f3f1", size = 141951, upload-time = "2026-03-04T14:17:17.961Z" }, + { url = "https://files.pythonhosted.org/packages/95/3c/87c42b4bd6dd297536f04cd9383d212ac557ecd49f2cbdcd46da1c9ef5c8/opentelemetry_sdk-1.45.1-py3-none-any.whl", hash = "sha256:c604c11dc429810812348989115fa44bd558772a3d7442afc43d024f2c250ca4", size = 140063, upload-time = "2026-10-06T17:32:55.04Z" }, ] [[package]] name = "opentelemetry-semantic-conventions" -version = "0.61b0" +version = "0.66b1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6d/c0/4ae7973f3c2cfd2b6e321f1675626f0dab0a97027cc7a297474c9c8f3d04/opentelemetry_semantic_conventions-0.61b0.tar.gz", hash = "sha256:072f65473c5d7c6dc0355b27d6c9d1a679d63b6d4b4b16a9773062cb7e31192a", size = 145755, upload-time = "2026-03-04T14:17:32.664Z" } +sdist = { url = "https://files.pythonhosted.org/packages/46/e4/dbbfb2a010c4db2224a5114638acede6fe563d33cc20fb1752cebcbe6298/opentelemetry_semantic_conventions-0.66b1.tar.gz", hash = "sha256:497ca63bf383723411e8eaf60c8779e9877633c936bb641080adab59d0eb6ec8", size = 150250, upload-time = "2026-10-06T17:33:14.073Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b2/37/cc6a55e448deaa9b27377d087da8615a3416d8ad523d5960b78dbeadd02a/opentelemetry_semantic_conventions-0.61b0-py3-none-any.whl", hash = "sha256:fa530a96be229795f8cef353739b618148b0fe2b4b3f005e60e262926c4d38e2", size = 231621, upload-time = "2026-03-04T14:17:19.33Z" }, + { url = "https://files.pythonhosted.org/packages/bc/14/67f8aa798857f8cf686f515bf93d9bb877ce952ddc8efae0fa25b45ce0d6/opentelemetry_semantic_conventions-0.66b1-py3-none-any.whl", hash = "sha256:d4cddeb4315490b35213f55e2bdc9ac54bb1e4d318927475bed62b35545e581b", size = 206279, upload-time = "2026-10-06T17:32:56.103Z" }, ] [[package]] @@ -512,7 +530,7 @@ wheels = [ [[package]] name = "posthog" -version = "7.75.4" +version = "7.75.11" source = { directory = "../../" } dependencies = [ { name = "backoff" }, @@ -877,12 +895,3 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/46/78/10ad9781128ed2f99dbc474f43283b13fea8ba58723e98844367531c18e9/wrapt-1.17.3-cp314-cp314t-win_arm64.whl", hash = "sha256:f38e60678850c42461d4202739f9bf1e3a737c7ad283638251e79cc49effb6b6", size = 38471, upload-time = "2025-08-12T05:52:57.784Z" }, { url = "https://files.pythonhosted.org/packages/1f/f6/a933bd70f98e9cf3e08167fc5cd7aaaca49147e48411c0bd5ae701bb2194/wrapt-1.17.3-py3-none-any.whl", hash = "sha256:7171ae35d2c33d326ac19dd8facb1e82e5fd04ef8c6c0e394d7af55a55051c22", size = 23591, upload-time = "2025-08-12T05:53:20.674Z" }, ] - -[[package]] -name = "zipp" -version = "3.23.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166", size = 25547, upload-time = "2025-06-08T17:06:39.4Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276, upload-time = "2025-06-08T17:06:38.034Z" }, -] diff --git a/examples/example-ai-ollama/uv.lock b/examples/example-ai-ollama/uv.lock index 88ab4389..e55d5b5d 100644 --- a/examples/example-ai-ollama/uv.lock +++ b/examples/example-ai-ollama/uv.lock @@ -262,18 +262,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/57/b0/0e52c878c53f245edd3a11020f20979b3f490f245af532c7cae3027754b5/idna-3.19-py3-none-any.whl", hash = "sha256:815e7be7a7806d54abb586dc943addc79e8b2ee16915059658cbeff4b1b43bf4", size = 68550, upload-time = "2026-08-18T05:14:22.343Z" }, ] -[[package]] -name = "importlib-metadata" -version = "8.7.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "zipp" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/f3/49/3b30cad09e7771a4982d9975a8cbf64f00d4a1ececb53297f1d9a7be1b10/importlib_metadata-8.7.1.tar.gz", hash = "sha256:49fef1ae6440c182052f407c8d34a68f72efc36db9ca90dc0113398f2fdde8bb", size = 57107, upload-time = "2025-12-21T10:00:19.278Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fa/5e/f8e9a1d23b9c20a551a8a02ea3637b4642e22c2626e3a13a9a29cdea99eb/importlib_metadata-8.7.1-py3-none-any.whl", hash = "sha256:5a1f80bf1daa489495071efbb095d75a634cf28a8bc299581244063b53176151", size = 27865, upload-time = "2025-12-21T10:00:18.329Z" }, -] - [[package]] name = "jiter" version = "0.13.0" @@ -392,50 +380,80 @@ wheels = [ [[package]] name = "opentelemetry-api" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "importlib-metadata" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2c/1d/4049a9e8698361cc1a1aa03a6c59e4fa4c71e0c0f94a30f988a6876a2ae6/opentelemetry_api-1.40.0.tar.gz", hash = "sha256:159be641c0b04d11e9ecd576906462773eb97ae1b657730f0ecf64d32071569f", size = 70851, upload-time = "2026-03-04T14:17:21.555Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2e/02/6e0ae9cc61bd3169d401077b507b3ebc344745171e1051ab430be012dcd9/opentelemetry_api-1.45.1.tar.gz", hash = "sha256:aa38ed19bcc084ba42782a73255b3582283eced7ad6dddbd6695189e69adfb75", size = 72804, upload-time = "2026-10-06T17:32:58.133Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/41/f7dcf80b81ee8e71c1a2b59f14208bc723edbd89ed027a73b175abf6348e/opentelemetry_api-1.45.1-py3-none-any.whl", hash = "sha256:b31553efa588ae44bc306f863c785c5333a9ecc091248c6ee68b4b6c87fdedfb", size = 60256, upload-time = "2026-10-06T17:32:33.506Z" }, +] + +[[package]] +name = "opentelemetry-exporter-http-transport" +version = "0.66b1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/62/0c/e3ebdb4b507f66afcc905e6885a4946969bd75b45988492643356fbbdc63/opentelemetry_exporter_http_transport-0.66b1.tar.gz", hash = "sha256:443080203bf52586ce0b2ad901e8951c61833eab1aa539ae6f1f16fe9e8e7952", size = 11693, upload-time = "2026-10-06T17:32:59.65Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/69/6af86ff66492b481c6a4c05dcfd68beb47ed8ba046440a26a2aac76b95c7/opentelemetry_exporter_http_transport-0.66b1-py3-none-any.whl", hash = "sha256:2f95404bdee7f9d2d529c7de56c7bd86d014d774d8fbf137810e0167f8a492bf", size = 12155, upload-time = "2026-10-06T17:32:35.454Z" }, +] + +[package.optional-dependencies] +requests = [ + { name = "requests" }, +] + +[[package]] +name = "opentelemetry-exporter-otlp-common" +version = "0.66b1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-sdk" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cb/19/41de712173f43057e4532d42ece7d0c6d4210d353e5752433cb14987643f/opentelemetry_exporter_otlp_common-0.66b1.tar.gz", hash = "sha256:6b1403487a2185ac1feb45fd5546fdf8630ce71c36bcefaadf51e2130e9e23f9", size = 14325, upload-time = "2026-10-06T17:33:01.725Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5f/bf/93795954016c522008da367da292adceed71cca6ee1717e1d64c83089099/opentelemetry_api-1.40.0-py3-none-any.whl", hash = "sha256:82dd69331ae74b06f6a874704be0cfaa49a1650e1537d4a813b86ecef7d0ecf9", size = 68676, upload-time = "2026-03-04T14:17:01.24Z" }, + { url = "https://files.pythonhosted.org/packages/fc/39/8c23d67665c762aa51840fa06f86e902e8f6f1693bc8d7e3d98cd6e2f753/opentelemetry_exporter_otlp_common-0.66b1-py3-none-any.whl", hash = "sha256:00ff8592c3a7cb729ff3fdc7ffa12372c243bdf2163e80c180994d0c7bd83ee9", size = 12385, upload-time = "2026-10-06T17:32:38.177Z" }, ] [[package]] name = "opentelemetry-exporter-otlp-proto-common" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-proto" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/51/bc/1559d46557fe6eca0b46c88d4c2676285f1f3be2e8d06bb5d15fbffc814a/opentelemetry_exporter_otlp_proto_common-1.40.0.tar.gz", hash = "sha256:1cbee86a4064790b362a86601ee7934f368b81cd4cc2f2e163902a6e7818a0fa", size = 20416, upload-time = "2026-03-04T14:17:23.801Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c1/8e/65e85e5137991a3c493b11682151d198638a5bc1dd4b4c5f67e013c57d7c/opentelemetry_exporter_otlp_proto_common-1.45.1.tar.gz", hash = "sha256:2e4adcc3a67bcf57804fc49514f0ef64974ca7590aa3491da389852b4a0628f6", size = 18873, upload-time = "2026-10-06T17:33:04.471Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8b/ca/8f122055c97a932311a3f640273f084e738008933503d0c2563cd5d591fc/opentelemetry_exporter_otlp_proto_common-1.40.0-py3-none-any.whl", hash = "sha256:7081ff453835a82417bf38dccf122c827c3cbc94f2079b03bba02a3165f25149", size = 18369, upload-time = "2026-03-04T14:17:04.796Z" }, + { url = "https://files.pythonhosted.org/packages/84/aa/92f225d353904e7f70b8b3e3c1b02db0cf56f744c2e83c581dc372e78873/opentelemetry_exporter_otlp_proto_common-1.45.1-py3-none-any.whl", hash = "sha256:2f446183ae7047b036226f1d846c41a834b0e8755ad13b51a51dd38952eb466c", size = 15393, upload-time = "2026-10-06T17:32:41.911Z" }, ] [[package]] name = "opentelemetry-exporter-otlp-proto-http" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "googleapis-common-protos" }, { name = "opentelemetry-api" }, + { name = "opentelemetry-exporter-http-transport", extra = ["requests"] }, + { name = "opentelemetry-exporter-otlp-common" }, { name = "opentelemetry-exporter-otlp-proto-common" }, { name = "opentelemetry-proto" }, { name = "opentelemetry-sdk" }, { name = "requests" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2e/fa/73d50e2c15c56be4d000c98e24221d494674b0cc95524e2a8cb3856d95a4/opentelemetry_exporter_otlp_proto_http-1.40.0.tar.gz", hash = "sha256:db48f5e0f33217588bbc00274a31517ba830da576e59503507c839b38fa0869c", size = 17772, upload-time = "2026-03-04T14:17:25.324Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/17/26487707ea4caa97b17e6e4b5fa72133a53512ffa2f5cf7a49ef284b29cb/opentelemetry_exporter_otlp_proto_http-1.45.1.tar.gz", hash = "sha256:45c218405ce3fd879596924b1874bf9a8f6880206d61065c5a912c8e5c297fb7", size = 28839, upload-time = "2026-10-06T17:33:05.713Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/3a/8865d6754e61c9fb170cdd530a124a53769ee5f740236064816eb0ca7301/opentelemetry_exporter_otlp_proto_http-1.40.0-py3-none-any.whl", hash = "sha256:a8d1dab28f504c5d96577d6509f80a8150e44e8f45f82cdbe0e34c99ab040069", size = 19960, upload-time = "2026-03-04T14:17:07.153Z" }, + { url = "https://files.pythonhosted.org/packages/aa/1f/517eaa0187ba106a9da97160ce2add3a371812681dc440930b267f714e42/opentelemetry_exporter_otlp_proto_http-1.45.1-py3-none-any.whl", hash = "sha256:24a97cf3753c7fb52fad44a696e452ff371686339e2acf3309e2eda3d0230700", size = 22180, upload-time = "2026-10-06T17:32:43.946Z" }, ] [[package]] name = "opentelemetry-instrumentation" -version = "0.61b0" +version = "0.66b1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, @@ -443,9 +461,9 @@ dependencies = [ { name = "packaging" }, { name = "wrapt" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/da/37/6bf8e66bfcee5d3c6515b79cb2ee9ad05fe573c20f7ceb288d0e7eeec28c/opentelemetry_instrumentation-0.61b0.tar.gz", hash = "sha256:cb21b48db738c9de196eba6b805b4ff9de3b7f187e4bbf9a466fa170514f1fc7", size = 32606, upload-time = "2026-03-04T14:20:16.825Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a5/03/89e47ff8d52a4f83b343e6eb9ef1698ff45357216e5b6b2b21e0da5c5c7d/opentelemetry_instrumentation-0.66b1.tar.gz", hash = "sha256:e79a510f7d87c72d95e964ddb42193a0d9a75668c027d980eab032ea1322a5ce", size = 43308, upload-time = "2026-10-06T17:36:10.703Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d8/3e/f6f10f178b6316de67f0dfdbbb699a24fbe8917cf1743c1595fb9dcdd461/opentelemetry_instrumentation-0.61b0-py3-none-any.whl", hash = "sha256:92a93a280e69788e8f88391247cc530fd81f16f2b011979d4d6398f805cfbc63", size = 33448, upload-time = "2026-03-04T14:19:02.447Z" }, + { url = "https://files.pythonhosted.org/packages/da/b2/d1413681ff43e13ac9860df27e1226d3199ab0b97b352ceea41abcc660a5/opentelemetry_instrumentation-0.66b1-py3-none-any.whl", hash = "sha256:4c4aa14dc9a24a02325a9d4c42c4d0208dbb1374c2b1b8fe6c9392d59f3e1008", size = 36904, upload-time = "2026-10-06T17:35:11.663Z" }, ] [[package]] @@ -464,41 +482,41 @@ wheels = [ [[package]] name = "opentelemetry-proto" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "protobuf" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4c/77/dd38991db037fdfce45849491cb61de5ab000f49824a00230afb112a4392/opentelemetry_proto-1.40.0.tar.gz", hash = "sha256:03f639ca129ba513f5819810f5b1f42bcb371391405d99c168fe6937c62febcd", size = 45667, upload-time = "2026-03-04T14:17:31.194Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4b/7f/15f014fb195da6c2dbb6c71399b8e76824878718e94de6454038488eed28/opentelemetry_proto-1.45.1.tar.gz", hash = "sha256:79e0fb95e4616691a469439238aa9224d75779b3e108e895d1aa125ab29ca77c", size = 46488, upload-time = "2026-10-06T17:33:11.49Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b9/b2/189b2577dde745b15625b3214302605b1353436219d42b7912e77fa8dc24/opentelemetry_proto-1.40.0-py3-none-any.whl", hash = "sha256:266c4385d88923a23d63e353e9761af0f47a6ed0d486979777fe4de59dc9b25f", size = 72073, upload-time = "2026-03-04T14:17:16.673Z" }, + { url = "https://files.pythonhosted.org/packages/ab/9a/42ec8180a769516ae757e893b69736826efceac7332553915b4528a91c6d/opentelemetry_proto-1.45.1-py3-none-any.whl", hash = "sha256:f38e2a8413053c180cd3d2637fbb279673ec2f6a6e09c995aafa2f452c52b46e", size = 72488, upload-time = "2026-10-06T17:32:53.057Z" }, ] [[package]] name = "opentelemetry-sdk" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, { name = "opentelemetry-semantic-conventions" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/58/fd/3c3125b20ba18ce2155ba9ea74acb0ae5d25f8cd39cfd37455601b7955cc/opentelemetry_sdk-1.40.0.tar.gz", hash = "sha256:18e9f5ec20d859d268c7cb3c5198c8d105d073714db3de50b593b8c1345a48f2", size = 184252, upload-time = "2026-03-04T14:17:31.87Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/79/7392e21a1c8f0c61d90b223e31c7e48cb9d452e91a6b820ad24cca5f23c4/opentelemetry_sdk-1.45.1.tar.gz", hash = "sha256:63d24a6ca645019a631e6a51999c73e93adcac1196ca640b8ae78a7cc4762bf3", size = 218324, upload-time = "2026-10-06T17:33:13.26Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/c5/6a852903d8bfac758c6dc6e9a68b015d3c33f2f1be5e9591e0f4b69c7e0a/opentelemetry_sdk-1.40.0-py3-none-any.whl", hash = "sha256:787d2154a71f4b3d81f20524a8ce061b7db667d24e46753f32a7bc48f1c1f3f1", size = 141951, upload-time = "2026-03-04T14:17:17.961Z" }, + { url = "https://files.pythonhosted.org/packages/95/3c/87c42b4bd6dd297536f04cd9383d212ac557ecd49f2cbdcd46da1c9ef5c8/opentelemetry_sdk-1.45.1-py3-none-any.whl", hash = "sha256:c604c11dc429810812348989115fa44bd558772a3d7442afc43d024f2c250ca4", size = 140063, upload-time = "2026-10-06T17:32:55.04Z" }, ] [[package]] name = "opentelemetry-semantic-conventions" -version = "0.61b0" +version = "0.66b1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6d/c0/4ae7973f3c2cfd2b6e321f1675626f0dab0a97027cc7a297474c9c8f3d04/opentelemetry_semantic_conventions-0.61b0.tar.gz", hash = "sha256:072f65473c5d7c6dc0355b27d6c9d1a679d63b6d4b4b16a9773062cb7e31192a", size = 145755, upload-time = "2026-03-04T14:17:32.664Z" } +sdist = { url = "https://files.pythonhosted.org/packages/46/e4/dbbfb2a010c4db2224a5114638acede6fe563d33cc20fb1752cebcbe6298/opentelemetry_semantic_conventions-0.66b1.tar.gz", hash = "sha256:497ca63bf383723411e8eaf60c8779e9877633c936bb641080adab59d0eb6ec8", size = 150250, upload-time = "2026-10-06T17:33:14.073Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b2/37/cc6a55e448deaa9b27377d087da8615a3416d8ad523d5960b78dbeadd02a/opentelemetry_semantic_conventions-0.61b0-py3-none-any.whl", hash = "sha256:fa530a96be229795f8cef353739b618148b0fe2b4b3f005e60e262926c4d38e2", size = 231621, upload-time = "2026-03-04T14:17:19.33Z" }, + { url = "https://files.pythonhosted.org/packages/bc/14/67f8aa798857f8cf686f515bf93d9bb877ce952ddc8efae0fa25b45ce0d6/opentelemetry_semantic_conventions-0.66b1-py3-none-any.whl", hash = "sha256:d4cddeb4315490b35213f55e2bdc9ac54bb1e4d318927475bed62b35545e581b", size = 206279, upload-time = "2026-10-06T17:32:56.103Z" }, ] [[package]] @@ -512,7 +530,7 @@ wheels = [ [[package]] name = "posthog" -version = "7.75.4" +version = "7.75.11" source = { directory = "../../" } dependencies = [ { name = "backoff" }, @@ -877,12 +895,3 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/46/78/10ad9781128ed2f99dbc474f43283b13fea8ba58723e98844367531c18e9/wrapt-1.17.3-cp314-cp314t-win_arm64.whl", hash = "sha256:f38e60678850c42461d4202739f9bf1e3a737c7ad283638251e79cc49effb6b6", size = 38471, upload-time = "2025-08-12T05:52:57.784Z" }, { url = "https://files.pythonhosted.org/packages/1f/f6/a933bd70f98e9cf3e08167fc5cd7aaaca49147e48411c0bd5ae701bb2194/wrapt-1.17.3-py3-none-any.whl", hash = "sha256:7171ae35d2c33d326ac19dd8facb1e82e5fd04ef8c6c0e394d7af55a55051c22", size = 23591, upload-time = "2025-08-12T05:53:20.674Z" }, ] - -[[package]] -name = "zipp" -version = "3.23.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166", size = 25547, upload-time = "2025-06-08T17:06:39.4Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276, upload-time = "2025-06-08T17:06:38.034Z" }, -] diff --git a/examples/example-ai-openai/uv.lock b/examples/example-ai-openai/uv.lock index abd252aa..b7cb9933 100644 --- a/examples/example-ai-openai/uv.lock +++ b/examples/example-ai-openai/uv.lock @@ -262,18 +262,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/57/b0/0e52c878c53f245edd3a11020f20979b3f490f245af532c7cae3027754b5/idna-3.19-py3-none-any.whl", hash = "sha256:815e7be7a7806d54abb586dc943addc79e8b2ee16915059658cbeff4b1b43bf4", size = 68550, upload-time = "2026-08-18T05:14:22.343Z" }, ] -[[package]] -name = "importlib-metadata" -version = "8.7.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "zipp" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/f3/49/3b30cad09e7771a4982d9975a8cbf64f00d4a1ececb53297f1d9a7be1b10/importlib_metadata-8.7.1.tar.gz", hash = "sha256:49fef1ae6440c182052f407c8d34a68f72efc36db9ca90dc0113398f2fdde8bb", size = 57107, upload-time = "2025-12-21T10:00:19.278Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fa/5e/f8e9a1d23b9c20a551a8a02ea3637b4642e22c2626e3a13a9a29cdea99eb/importlib_metadata-8.7.1-py3-none-any.whl", hash = "sha256:5a1f80bf1daa489495071efbb095d75a634cf28a8bc299581244063b53176151", size = 27865, upload-time = "2025-12-21T10:00:18.329Z" }, -] - [[package]] name = "jiter" version = "0.13.0" @@ -392,50 +380,80 @@ wheels = [ [[package]] name = "opentelemetry-api" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "importlib-metadata" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2c/1d/4049a9e8698361cc1a1aa03a6c59e4fa4c71e0c0f94a30f988a6876a2ae6/opentelemetry_api-1.40.0.tar.gz", hash = "sha256:159be641c0b04d11e9ecd576906462773eb97ae1b657730f0ecf64d32071569f", size = 70851, upload-time = "2026-03-04T14:17:21.555Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2e/02/6e0ae9cc61bd3169d401077b507b3ebc344745171e1051ab430be012dcd9/opentelemetry_api-1.45.1.tar.gz", hash = "sha256:aa38ed19bcc084ba42782a73255b3582283eced7ad6dddbd6695189e69adfb75", size = 72804, upload-time = "2026-10-06T17:32:58.133Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/41/f7dcf80b81ee8e71c1a2b59f14208bc723edbd89ed027a73b175abf6348e/opentelemetry_api-1.45.1-py3-none-any.whl", hash = "sha256:b31553efa588ae44bc306f863c785c5333a9ecc091248c6ee68b4b6c87fdedfb", size = 60256, upload-time = "2026-10-06T17:32:33.506Z" }, +] + +[[package]] +name = "opentelemetry-exporter-http-transport" +version = "0.66b1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/62/0c/e3ebdb4b507f66afcc905e6885a4946969bd75b45988492643356fbbdc63/opentelemetry_exporter_http_transport-0.66b1.tar.gz", hash = "sha256:443080203bf52586ce0b2ad901e8951c61833eab1aa539ae6f1f16fe9e8e7952", size = 11693, upload-time = "2026-10-06T17:32:59.65Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/69/6af86ff66492b481c6a4c05dcfd68beb47ed8ba046440a26a2aac76b95c7/opentelemetry_exporter_http_transport-0.66b1-py3-none-any.whl", hash = "sha256:2f95404bdee7f9d2d529c7de56c7bd86d014d774d8fbf137810e0167f8a492bf", size = 12155, upload-time = "2026-10-06T17:32:35.454Z" }, +] + +[package.optional-dependencies] +requests = [ + { name = "requests" }, +] + +[[package]] +name = "opentelemetry-exporter-otlp-common" +version = "0.66b1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-sdk" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cb/19/41de712173f43057e4532d42ece7d0c6d4210d353e5752433cb14987643f/opentelemetry_exporter_otlp_common-0.66b1.tar.gz", hash = "sha256:6b1403487a2185ac1feb45fd5546fdf8630ce71c36bcefaadf51e2130e9e23f9", size = 14325, upload-time = "2026-10-06T17:33:01.725Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5f/bf/93795954016c522008da367da292adceed71cca6ee1717e1d64c83089099/opentelemetry_api-1.40.0-py3-none-any.whl", hash = "sha256:82dd69331ae74b06f6a874704be0cfaa49a1650e1537d4a813b86ecef7d0ecf9", size = 68676, upload-time = "2026-03-04T14:17:01.24Z" }, + { url = "https://files.pythonhosted.org/packages/fc/39/8c23d67665c762aa51840fa06f86e902e8f6f1693bc8d7e3d98cd6e2f753/opentelemetry_exporter_otlp_common-0.66b1-py3-none-any.whl", hash = "sha256:00ff8592c3a7cb729ff3fdc7ffa12372c243bdf2163e80c180994d0c7bd83ee9", size = 12385, upload-time = "2026-10-06T17:32:38.177Z" }, ] [[package]] name = "opentelemetry-exporter-otlp-proto-common" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-proto" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/51/bc/1559d46557fe6eca0b46c88d4c2676285f1f3be2e8d06bb5d15fbffc814a/opentelemetry_exporter_otlp_proto_common-1.40.0.tar.gz", hash = "sha256:1cbee86a4064790b362a86601ee7934f368b81cd4cc2f2e163902a6e7818a0fa", size = 20416, upload-time = "2026-03-04T14:17:23.801Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c1/8e/65e85e5137991a3c493b11682151d198638a5bc1dd4b4c5f67e013c57d7c/opentelemetry_exporter_otlp_proto_common-1.45.1.tar.gz", hash = "sha256:2e4adcc3a67bcf57804fc49514f0ef64974ca7590aa3491da389852b4a0628f6", size = 18873, upload-time = "2026-10-06T17:33:04.471Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8b/ca/8f122055c97a932311a3f640273f084e738008933503d0c2563cd5d591fc/opentelemetry_exporter_otlp_proto_common-1.40.0-py3-none-any.whl", hash = "sha256:7081ff453835a82417bf38dccf122c827c3cbc94f2079b03bba02a3165f25149", size = 18369, upload-time = "2026-03-04T14:17:04.796Z" }, + { url = "https://files.pythonhosted.org/packages/84/aa/92f225d353904e7f70b8b3e3c1b02db0cf56f744c2e83c581dc372e78873/opentelemetry_exporter_otlp_proto_common-1.45.1-py3-none-any.whl", hash = "sha256:2f446183ae7047b036226f1d846c41a834b0e8755ad13b51a51dd38952eb466c", size = 15393, upload-time = "2026-10-06T17:32:41.911Z" }, ] [[package]] name = "opentelemetry-exporter-otlp-proto-http" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "googleapis-common-protos" }, { name = "opentelemetry-api" }, + { name = "opentelemetry-exporter-http-transport", extra = ["requests"] }, + { name = "opentelemetry-exporter-otlp-common" }, { name = "opentelemetry-exporter-otlp-proto-common" }, { name = "opentelemetry-proto" }, { name = "opentelemetry-sdk" }, { name = "requests" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2e/fa/73d50e2c15c56be4d000c98e24221d494674b0cc95524e2a8cb3856d95a4/opentelemetry_exporter_otlp_proto_http-1.40.0.tar.gz", hash = "sha256:db48f5e0f33217588bbc00274a31517ba830da576e59503507c839b38fa0869c", size = 17772, upload-time = "2026-03-04T14:17:25.324Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/17/26487707ea4caa97b17e6e4b5fa72133a53512ffa2f5cf7a49ef284b29cb/opentelemetry_exporter_otlp_proto_http-1.45.1.tar.gz", hash = "sha256:45c218405ce3fd879596924b1874bf9a8f6880206d61065c5a912c8e5c297fb7", size = 28839, upload-time = "2026-10-06T17:33:05.713Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/3a/8865d6754e61c9fb170cdd530a124a53769ee5f740236064816eb0ca7301/opentelemetry_exporter_otlp_proto_http-1.40.0-py3-none-any.whl", hash = "sha256:a8d1dab28f504c5d96577d6509f80a8150e44e8f45f82cdbe0e34c99ab040069", size = 19960, upload-time = "2026-03-04T14:17:07.153Z" }, + { url = "https://files.pythonhosted.org/packages/aa/1f/517eaa0187ba106a9da97160ce2add3a371812681dc440930b267f714e42/opentelemetry_exporter_otlp_proto_http-1.45.1-py3-none-any.whl", hash = "sha256:24a97cf3753c7fb52fad44a696e452ff371686339e2acf3309e2eda3d0230700", size = 22180, upload-time = "2026-10-06T17:32:43.946Z" }, ] [[package]] name = "opentelemetry-instrumentation" -version = "0.61b0" +version = "0.66b1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, @@ -443,9 +461,9 @@ dependencies = [ { name = "packaging" }, { name = "wrapt" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/da/37/6bf8e66bfcee5d3c6515b79cb2ee9ad05fe573c20f7ceb288d0e7eeec28c/opentelemetry_instrumentation-0.61b0.tar.gz", hash = "sha256:cb21b48db738c9de196eba6b805b4ff9de3b7f187e4bbf9a466fa170514f1fc7", size = 32606, upload-time = "2026-03-04T14:20:16.825Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a5/03/89e47ff8d52a4f83b343e6eb9ef1698ff45357216e5b6b2b21e0da5c5c7d/opentelemetry_instrumentation-0.66b1.tar.gz", hash = "sha256:e79a510f7d87c72d95e964ddb42193a0d9a75668c027d980eab032ea1322a5ce", size = 43308, upload-time = "2026-10-06T17:36:10.703Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d8/3e/f6f10f178b6316de67f0dfdbbb699a24fbe8917cf1743c1595fb9dcdd461/opentelemetry_instrumentation-0.61b0-py3-none-any.whl", hash = "sha256:92a93a280e69788e8f88391247cc530fd81f16f2b011979d4d6398f805cfbc63", size = 33448, upload-time = "2026-03-04T14:19:02.447Z" }, + { url = "https://files.pythonhosted.org/packages/da/b2/d1413681ff43e13ac9860df27e1226d3199ab0b97b352ceea41abcc660a5/opentelemetry_instrumentation-0.66b1-py3-none-any.whl", hash = "sha256:4c4aa14dc9a24a02325a9d4c42c4d0208dbb1374c2b1b8fe6c9392d59f3e1008", size = 36904, upload-time = "2026-10-06T17:35:11.663Z" }, ] [[package]] @@ -464,41 +482,41 @@ wheels = [ [[package]] name = "opentelemetry-proto" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "protobuf" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4c/77/dd38991db037fdfce45849491cb61de5ab000f49824a00230afb112a4392/opentelemetry_proto-1.40.0.tar.gz", hash = "sha256:03f639ca129ba513f5819810f5b1f42bcb371391405d99c168fe6937c62febcd", size = 45667, upload-time = "2026-03-04T14:17:31.194Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4b/7f/15f014fb195da6c2dbb6c71399b8e76824878718e94de6454038488eed28/opentelemetry_proto-1.45.1.tar.gz", hash = "sha256:79e0fb95e4616691a469439238aa9224d75779b3e108e895d1aa125ab29ca77c", size = 46488, upload-time = "2026-10-06T17:33:11.49Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b9/b2/189b2577dde745b15625b3214302605b1353436219d42b7912e77fa8dc24/opentelemetry_proto-1.40.0-py3-none-any.whl", hash = "sha256:266c4385d88923a23d63e353e9761af0f47a6ed0d486979777fe4de59dc9b25f", size = 72073, upload-time = "2026-03-04T14:17:16.673Z" }, + { url = "https://files.pythonhosted.org/packages/ab/9a/42ec8180a769516ae757e893b69736826efceac7332553915b4528a91c6d/opentelemetry_proto-1.45.1-py3-none-any.whl", hash = "sha256:f38e2a8413053c180cd3d2637fbb279673ec2f6a6e09c995aafa2f452c52b46e", size = 72488, upload-time = "2026-10-06T17:32:53.057Z" }, ] [[package]] name = "opentelemetry-sdk" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, { name = "opentelemetry-semantic-conventions" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/58/fd/3c3125b20ba18ce2155ba9ea74acb0ae5d25f8cd39cfd37455601b7955cc/opentelemetry_sdk-1.40.0.tar.gz", hash = "sha256:18e9f5ec20d859d268c7cb3c5198c8d105d073714db3de50b593b8c1345a48f2", size = 184252, upload-time = "2026-03-04T14:17:31.87Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/79/7392e21a1c8f0c61d90b223e31c7e48cb9d452e91a6b820ad24cca5f23c4/opentelemetry_sdk-1.45.1.tar.gz", hash = "sha256:63d24a6ca645019a631e6a51999c73e93adcac1196ca640b8ae78a7cc4762bf3", size = 218324, upload-time = "2026-10-06T17:33:13.26Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/c5/6a852903d8bfac758c6dc6e9a68b015d3c33f2f1be5e9591e0f4b69c7e0a/opentelemetry_sdk-1.40.0-py3-none-any.whl", hash = "sha256:787d2154a71f4b3d81f20524a8ce061b7db667d24e46753f32a7bc48f1c1f3f1", size = 141951, upload-time = "2026-03-04T14:17:17.961Z" }, + { url = "https://files.pythonhosted.org/packages/95/3c/87c42b4bd6dd297536f04cd9383d212ac557ecd49f2cbdcd46da1c9ef5c8/opentelemetry_sdk-1.45.1-py3-none-any.whl", hash = "sha256:c604c11dc429810812348989115fa44bd558772a3d7442afc43d024f2c250ca4", size = 140063, upload-time = "2026-10-06T17:32:55.04Z" }, ] [[package]] name = "opentelemetry-semantic-conventions" -version = "0.61b0" +version = "0.66b1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6d/c0/4ae7973f3c2cfd2b6e321f1675626f0dab0a97027cc7a297474c9c8f3d04/opentelemetry_semantic_conventions-0.61b0.tar.gz", hash = "sha256:072f65473c5d7c6dc0355b27d6c9d1a679d63b6d4b4b16a9773062cb7e31192a", size = 145755, upload-time = "2026-03-04T14:17:32.664Z" } +sdist = { url = "https://files.pythonhosted.org/packages/46/e4/dbbfb2a010c4db2224a5114638acede6fe563d33cc20fb1752cebcbe6298/opentelemetry_semantic_conventions-0.66b1.tar.gz", hash = "sha256:497ca63bf383723411e8eaf60c8779e9877633c936bb641080adab59d0eb6ec8", size = 150250, upload-time = "2026-10-06T17:33:14.073Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b2/37/cc6a55e448deaa9b27377d087da8615a3416d8ad523d5960b78dbeadd02a/opentelemetry_semantic_conventions-0.61b0-py3-none-any.whl", hash = "sha256:fa530a96be229795f8cef353739b618148b0fe2b4b3f005e60e262926c4d38e2", size = 231621, upload-time = "2026-03-04T14:17:19.33Z" }, + { url = "https://files.pythonhosted.org/packages/bc/14/67f8aa798857f8cf686f515bf93d9bb877ce952ddc8efae0fa25b45ce0d6/opentelemetry_semantic_conventions-0.66b1-py3-none-any.whl", hash = "sha256:d4cddeb4315490b35213f55e2bdc9ac54bb1e4d318927475bed62b35545e581b", size = 206279, upload-time = "2026-10-06T17:32:56.103Z" }, ] [[package]] @@ -512,7 +530,7 @@ wheels = [ [[package]] name = "posthog" -version = "7.75.4" +version = "7.75.11" source = { directory = "../../" } dependencies = [ { name = "backoff" }, @@ -877,12 +895,3 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/46/78/10ad9781128ed2f99dbc474f43283b13fea8ba58723e98844367531c18e9/wrapt-1.17.3-cp314-cp314t-win_arm64.whl", hash = "sha256:f38e60678850c42461d4202739f9bf1e3a737c7ad283638251e79cc49effb6b6", size = 38471, upload-time = "2025-08-12T05:52:57.784Z" }, { url = "https://files.pythonhosted.org/packages/1f/f6/a933bd70f98e9cf3e08167fc5cd7aaaca49147e48411c0bd5ae701bb2194/wrapt-1.17.3-py3-none-any.whl", hash = "sha256:7171ae35d2c33d326ac19dd8facb1e82e5fd04ef8c6c0e394d7af55a55051c22", size = 23591, upload-time = "2025-08-12T05:53:20.674Z" }, ] - -[[package]] -name = "zipp" -version = "3.23.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166", size = 25547, upload-time = "2025-06-08T17:06:39.4Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276, upload-time = "2025-06-08T17:06:38.034Z" }, -] diff --git a/examples/example-ai-openrouter/uv.lock b/examples/example-ai-openrouter/uv.lock index 2468a06a..7d07fff2 100644 --- a/examples/example-ai-openrouter/uv.lock +++ b/examples/example-ai-openrouter/uv.lock @@ -262,18 +262,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/57/b0/0e52c878c53f245edd3a11020f20979b3f490f245af532c7cae3027754b5/idna-3.19-py3-none-any.whl", hash = "sha256:815e7be7a7806d54abb586dc943addc79e8b2ee16915059658cbeff4b1b43bf4", size = 68550, upload-time = "2026-08-18T05:14:22.343Z" }, ] -[[package]] -name = "importlib-metadata" -version = "8.7.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "zipp" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/f3/49/3b30cad09e7771a4982d9975a8cbf64f00d4a1ececb53297f1d9a7be1b10/importlib_metadata-8.7.1.tar.gz", hash = "sha256:49fef1ae6440c182052f407c8d34a68f72efc36db9ca90dc0113398f2fdde8bb", size = 57107, upload-time = "2025-12-21T10:00:19.278Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fa/5e/f8e9a1d23b9c20a551a8a02ea3637b4642e22c2626e3a13a9a29cdea99eb/importlib_metadata-8.7.1-py3-none-any.whl", hash = "sha256:5a1f80bf1daa489495071efbb095d75a634cf28a8bc299581244063b53176151", size = 27865, upload-time = "2025-12-21T10:00:18.329Z" }, -] - [[package]] name = "jiter" version = "0.13.0" @@ -392,50 +380,80 @@ wheels = [ [[package]] name = "opentelemetry-api" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "importlib-metadata" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2c/1d/4049a9e8698361cc1a1aa03a6c59e4fa4c71e0c0f94a30f988a6876a2ae6/opentelemetry_api-1.40.0.tar.gz", hash = "sha256:159be641c0b04d11e9ecd576906462773eb97ae1b657730f0ecf64d32071569f", size = 70851, upload-time = "2026-03-04T14:17:21.555Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2e/02/6e0ae9cc61bd3169d401077b507b3ebc344745171e1051ab430be012dcd9/opentelemetry_api-1.45.1.tar.gz", hash = "sha256:aa38ed19bcc084ba42782a73255b3582283eced7ad6dddbd6695189e69adfb75", size = 72804, upload-time = "2026-10-06T17:32:58.133Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/41/f7dcf80b81ee8e71c1a2b59f14208bc723edbd89ed027a73b175abf6348e/opentelemetry_api-1.45.1-py3-none-any.whl", hash = "sha256:b31553efa588ae44bc306f863c785c5333a9ecc091248c6ee68b4b6c87fdedfb", size = 60256, upload-time = "2026-10-06T17:32:33.506Z" }, +] + +[[package]] +name = "opentelemetry-exporter-http-transport" +version = "0.66b1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/62/0c/e3ebdb4b507f66afcc905e6885a4946969bd75b45988492643356fbbdc63/opentelemetry_exporter_http_transport-0.66b1.tar.gz", hash = "sha256:443080203bf52586ce0b2ad901e8951c61833eab1aa539ae6f1f16fe9e8e7952", size = 11693, upload-time = "2026-10-06T17:32:59.65Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/69/6af86ff66492b481c6a4c05dcfd68beb47ed8ba046440a26a2aac76b95c7/opentelemetry_exporter_http_transport-0.66b1-py3-none-any.whl", hash = "sha256:2f95404bdee7f9d2d529c7de56c7bd86d014d774d8fbf137810e0167f8a492bf", size = 12155, upload-time = "2026-10-06T17:32:35.454Z" }, +] + +[package.optional-dependencies] +requests = [ + { name = "requests" }, +] + +[[package]] +name = "opentelemetry-exporter-otlp-common" +version = "0.66b1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-sdk" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cb/19/41de712173f43057e4532d42ece7d0c6d4210d353e5752433cb14987643f/opentelemetry_exporter_otlp_common-0.66b1.tar.gz", hash = "sha256:6b1403487a2185ac1feb45fd5546fdf8630ce71c36bcefaadf51e2130e9e23f9", size = 14325, upload-time = "2026-10-06T17:33:01.725Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5f/bf/93795954016c522008da367da292adceed71cca6ee1717e1d64c83089099/opentelemetry_api-1.40.0-py3-none-any.whl", hash = "sha256:82dd69331ae74b06f6a874704be0cfaa49a1650e1537d4a813b86ecef7d0ecf9", size = 68676, upload-time = "2026-03-04T14:17:01.24Z" }, + { url = "https://files.pythonhosted.org/packages/fc/39/8c23d67665c762aa51840fa06f86e902e8f6f1693bc8d7e3d98cd6e2f753/opentelemetry_exporter_otlp_common-0.66b1-py3-none-any.whl", hash = "sha256:00ff8592c3a7cb729ff3fdc7ffa12372c243bdf2163e80c180994d0c7bd83ee9", size = 12385, upload-time = "2026-10-06T17:32:38.177Z" }, ] [[package]] name = "opentelemetry-exporter-otlp-proto-common" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-proto" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/51/bc/1559d46557fe6eca0b46c88d4c2676285f1f3be2e8d06bb5d15fbffc814a/opentelemetry_exporter_otlp_proto_common-1.40.0.tar.gz", hash = "sha256:1cbee86a4064790b362a86601ee7934f368b81cd4cc2f2e163902a6e7818a0fa", size = 20416, upload-time = "2026-03-04T14:17:23.801Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c1/8e/65e85e5137991a3c493b11682151d198638a5bc1dd4b4c5f67e013c57d7c/opentelemetry_exporter_otlp_proto_common-1.45.1.tar.gz", hash = "sha256:2e4adcc3a67bcf57804fc49514f0ef64974ca7590aa3491da389852b4a0628f6", size = 18873, upload-time = "2026-10-06T17:33:04.471Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8b/ca/8f122055c97a932311a3f640273f084e738008933503d0c2563cd5d591fc/opentelemetry_exporter_otlp_proto_common-1.40.0-py3-none-any.whl", hash = "sha256:7081ff453835a82417bf38dccf122c827c3cbc94f2079b03bba02a3165f25149", size = 18369, upload-time = "2026-03-04T14:17:04.796Z" }, + { url = "https://files.pythonhosted.org/packages/84/aa/92f225d353904e7f70b8b3e3c1b02db0cf56f744c2e83c581dc372e78873/opentelemetry_exporter_otlp_proto_common-1.45.1-py3-none-any.whl", hash = "sha256:2f446183ae7047b036226f1d846c41a834b0e8755ad13b51a51dd38952eb466c", size = 15393, upload-time = "2026-10-06T17:32:41.911Z" }, ] [[package]] name = "opentelemetry-exporter-otlp-proto-http" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "googleapis-common-protos" }, { name = "opentelemetry-api" }, + { name = "opentelemetry-exporter-http-transport", extra = ["requests"] }, + { name = "opentelemetry-exporter-otlp-common" }, { name = "opentelemetry-exporter-otlp-proto-common" }, { name = "opentelemetry-proto" }, { name = "opentelemetry-sdk" }, { name = "requests" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2e/fa/73d50e2c15c56be4d000c98e24221d494674b0cc95524e2a8cb3856d95a4/opentelemetry_exporter_otlp_proto_http-1.40.0.tar.gz", hash = "sha256:db48f5e0f33217588bbc00274a31517ba830da576e59503507c839b38fa0869c", size = 17772, upload-time = "2026-03-04T14:17:25.324Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/17/26487707ea4caa97b17e6e4b5fa72133a53512ffa2f5cf7a49ef284b29cb/opentelemetry_exporter_otlp_proto_http-1.45.1.tar.gz", hash = "sha256:45c218405ce3fd879596924b1874bf9a8f6880206d61065c5a912c8e5c297fb7", size = 28839, upload-time = "2026-10-06T17:33:05.713Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/3a/8865d6754e61c9fb170cdd530a124a53769ee5f740236064816eb0ca7301/opentelemetry_exporter_otlp_proto_http-1.40.0-py3-none-any.whl", hash = "sha256:a8d1dab28f504c5d96577d6509f80a8150e44e8f45f82cdbe0e34c99ab040069", size = 19960, upload-time = "2026-03-04T14:17:07.153Z" }, + { url = "https://files.pythonhosted.org/packages/aa/1f/517eaa0187ba106a9da97160ce2add3a371812681dc440930b267f714e42/opentelemetry_exporter_otlp_proto_http-1.45.1-py3-none-any.whl", hash = "sha256:24a97cf3753c7fb52fad44a696e452ff371686339e2acf3309e2eda3d0230700", size = 22180, upload-time = "2026-10-06T17:32:43.946Z" }, ] [[package]] name = "opentelemetry-instrumentation" -version = "0.61b0" +version = "0.66b1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, @@ -443,9 +461,9 @@ dependencies = [ { name = "packaging" }, { name = "wrapt" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/da/37/6bf8e66bfcee5d3c6515b79cb2ee9ad05fe573c20f7ceb288d0e7eeec28c/opentelemetry_instrumentation-0.61b0.tar.gz", hash = "sha256:cb21b48db738c9de196eba6b805b4ff9de3b7f187e4bbf9a466fa170514f1fc7", size = 32606, upload-time = "2026-03-04T14:20:16.825Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a5/03/89e47ff8d52a4f83b343e6eb9ef1698ff45357216e5b6b2b21e0da5c5c7d/opentelemetry_instrumentation-0.66b1.tar.gz", hash = "sha256:e79a510f7d87c72d95e964ddb42193a0d9a75668c027d980eab032ea1322a5ce", size = 43308, upload-time = "2026-10-06T17:36:10.703Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d8/3e/f6f10f178b6316de67f0dfdbbb699a24fbe8917cf1743c1595fb9dcdd461/opentelemetry_instrumentation-0.61b0-py3-none-any.whl", hash = "sha256:92a93a280e69788e8f88391247cc530fd81f16f2b011979d4d6398f805cfbc63", size = 33448, upload-time = "2026-03-04T14:19:02.447Z" }, + { url = "https://files.pythonhosted.org/packages/da/b2/d1413681ff43e13ac9860df27e1226d3199ab0b97b352ceea41abcc660a5/opentelemetry_instrumentation-0.66b1-py3-none-any.whl", hash = "sha256:4c4aa14dc9a24a02325a9d4c42c4d0208dbb1374c2b1b8fe6c9392d59f3e1008", size = 36904, upload-time = "2026-10-06T17:35:11.663Z" }, ] [[package]] @@ -464,41 +482,41 @@ wheels = [ [[package]] name = "opentelemetry-proto" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "protobuf" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4c/77/dd38991db037fdfce45849491cb61de5ab000f49824a00230afb112a4392/opentelemetry_proto-1.40.0.tar.gz", hash = "sha256:03f639ca129ba513f5819810f5b1f42bcb371391405d99c168fe6937c62febcd", size = 45667, upload-time = "2026-03-04T14:17:31.194Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4b/7f/15f014fb195da6c2dbb6c71399b8e76824878718e94de6454038488eed28/opentelemetry_proto-1.45.1.tar.gz", hash = "sha256:79e0fb95e4616691a469439238aa9224d75779b3e108e895d1aa125ab29ca77c", size = 46488, upload-time = "2026-10-06T17:33:11.49Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b9/b2/189b2577dde745b15625b3214302605b1353436219d42b7912e77fa8dc24/opentelemetry_proto-1.40.0-py3-none-any.whl", hash = "sha256:266c4385d88923a23d63e353e9761af0f47a6ed0d486979777fe4de59dc9b25f", size = 72073, upload-time = "2026-03-04T14:17:16.673Z" }, + { url = "https://files.pythonhosted.org/packages/ab/9a/42ec8180a769516ae757e893b69736826efceac7332553915b4528a91c6d/opentelemetry_proto-1.45.1-py3-none-any.whl", hash = "sha256:f38e2a8413053c180cd3d2637fbb279673ec2f6a6e09c995aafa2f452c52b46e", size = 72488, upload-time = "2026-10-06T17:32:53.057Z" }, ] [[package]] name = "opentelemetry-sdk" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, { name = "opentelemetry-semantic-conventions" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/58/fd/3c3125b20ba18ce2155ba9ea74acb0ae5d25f8cd39cfd37455601b7955cc/opentelemetry_sdk-1.40.0.tar.gz", hash = "sha256:18e9f5ec20d859d268c7cb3c5198c8d105d073714db3de50b593b8c1345a48f2", size = 184252, upload-time = "2026-03-04T14:17:31.87Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/79/7392e21a1c8f0c61d90b223e31c7e48cb9d452e91a6b820ad24cca5f23c4/opentelemetry_sdk-1.45.1.tar.gz", hash = "sha256:63d24a6ca645019a631e6a51999c73e93adcac1196ca640b8ae78a7cc4762bf3", size = 218324, upload-time = "2026-10-06T17:33:13.26Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/c5/6a852903d8bfac758c6dc6e9a68b015d3c33f2f1be5e9591e0f4b69c7e0a/opentelemetry_sdk-1.40.0-py3-none-any.whl", hash = "sha256:787d2154a71f4b3d81f20524a8ce061b7db667d24e46753f32a7bc48f1c1f3f1", size = 141951, upload-time = "2026-03-04T14:17:17.961Z" }, + { url = "https://files.pythonhosted.org/packages/95/3c/87c42b4bd6dd297536f04cd9383d212ac557ecd49f2cbdcd46da1c9ef5c8/opentelemetry_sdk-1.45.1-py3-none-any.whl", hash = "sha256:c604c11dc429810812348989115fa44bd558772a3d7442afc43d024f2c250ca4", size = 140063, upload-time = "2026-10-06T17:32:55.04Z" }, ] [[package]] name = "opentelemetry-semantic-conventions" -version = "0.61b0" +version = "0.66b1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6d/c0/4ae7973f3c2cfd2b6e321f1675626f0dab0a97027cc7a297474c9c8f3d04/opentelemetry_semantic_conventions-0.61b0.tar.gz", hash = "sha256:072f65473c5d7c6dc0355b27d6c9d1a679d63b6d4b4b16a9773062cb7e31192a", size = 145755, upload-time = "2026-03-04T14:17:32.664Z" } +sdist = { url = "https://files.pythonhosted.org/packages/46/e4/dbbfb2a010c4db2224a5114638acede6fe563d33cc20fb1752cebcbe6298/opentelemetry_semantic_conventions-0.66b1.tar.gz", hash = "sha256:497ca63bf383723411e8eaf60c8779e9877633c936bb641080adab59d0eb6ec8", size = 150250, upload-time = "2026-10-06T17:33:14.073Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b2/37/cc6a55e448deaa9b27377d087da8615a3416d8ad523d5960b78dbeadd02a/opentelemetry_semantic_conventions-0.61b0-py3-none-any.whl", hash = "sha256:fa530a96be229795f8cef353739b618148b0fe2b4b3f005e60e262926c4d38e2", size = 231621, upload-time = "2026-03-04T14:17:19.33Z" }, + { url = "https://files.pythonhosted.org/packages/bc/14/67f8aa798857f8cf686f515bf93d9bb877ce952ddc8efae0fa25b45ce0d6/opentelemetry_semantic_conventions-0.66b1-py3-none-any.whl", hash = "sha256:d4cddeb4315490b35213f55e2bdc9ac54bb1e4d318927475bed62b35545e581b", size = 206279, upload-time = "2026-10-06T17:32:56.103Z" }, ] [[package]] @@ -512,7 +530,7 @@ wheels = [ [[package]] name = "posthog" -version = "7.75.4" +version = "7.75.11" source = { directory = "../../" } dependencies = [ { name = "backoff" }, @@ -877,12 +895,3 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/46/78/10ad9781128ed2f99dbc474f43283b13fea8ba58723e98844367531c18e9/wrapt-1.17.3-cp314-cp314t-win_arm64.whl", hash = "sha256:f38e60678850c42461d4202739f9bf1e3a737c7ad283638251e79cc49effb6b6", size = 38471, upload-time = "2025-08-12T05:52:57.784Z" }, { url = "https://files.pythonhosted.org/packages/1f/f6/a933bd70f98e9cf3e08167fc5cd7aaaca49147e48411c0bd5ae701bb2194/wrapt-1.17.3-py3-none-any.whl", hash = "sha256:7171ae35d2c33d326ac19dd8facb1e82e5fd04ef8c6c0e394d7af55a55051c22", size = 23591, upload-time = "2025-08-12T05:53:20.674Z" }, ] - -[[package]] -name = "zipp" -version = "3.23.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166", size = 25547, upload-time = "2025-06-08T17:06:39.4Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276, upload-time = "2025-06-08T17:06:38.034Z" }, -] diff --git a/examples/example-ai-perplexity/uv.lock b/examples/example-ai-perplexity/uv.lock index 45e91258..5863f63b 100644 --- a/examples/example-ai-perplexity/uv.lock +++ b/examples/example-ai-perplexity/uv.lock @@ -262,18 +262,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/57/b0/0e52c878c53f245edd3a11020f20979b3f490f245af532c7cae3027754b5/idna-3.19-py3-none-any.whl", hash = "sha256:815e7be7a7806d54abb586dc943addc79e8b2ee16915059658cbeff4b1b43bf4", size = 68550, upload-time = "2026-08-18T05:14:22.343Z" }, ] -[[package]] -name = "importlib-metadata" -version = "8.7.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "zipp" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/f3/49/3b30cad09e7771a4982d9975a8cbf64f00d4a1ececb53297f1d9a7be1b10/importlib_metadata-8.7.1.tar.gz", hash = "sha256:49fef1ae6440c182052f407c8d34a68f72efc36db9ca90dc0113398f2fdde8bb", size = 57107, upload-time = "2025-12-21T10:00:19.278Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fa/5e/f8e9a1d23b9c20a551a8a02ea3637b4642e22c2626e3a13a9a29cdea99eb/importlib_metadata-8.7.1-py3-none-any.whl", hash = "sha256:5a1f80bf1daa489495071efbb095d75a634cf28a8bc299581244063b53176151", size = 27865, upload-time = "2025-12-21T10:00:18.329Z" }, -] - [[package]] name = "jiter" version = "0.13.0" @@ -392,50 +380,80 @@ wheels = [ [[package]] name = "opentelemetry-api" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "importlib-metadata" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2c/1d/4049a9e8698361cc1a1aa03a6c59e4fa4c71e0c0f94a30f988a6876a2ae6/opentelemetry_api-1.40.0.tar.gz", hash = "sha256:159be641c0b04d11e9ecd576906462773eb97ae1b657730f0ecf64d32071569f", size = 70851, upload-time = "2026-03-04T14:17:21.555Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2e/02/6e0ae9cc61bd3169d401077b507b3ebc344745171e1051ab430be012dcd9/opentelemetry_api-1.45.1.tar.gz", hash = "sha256:aa38ed19bcc084ba42782a73255b3582283eced7ad6dddbd6695189e69adfb75", size = 72804, upload-time = "2026-10-06T17:32:58.133Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/41/f7dcf80b81ee8e71c1a2b59f14208bc723edbd89ed027a73b175abf6348e/opentelemetry_api-1.45.1-py3-none-any.whl", hash = "sha256:b31553efa588ae44bc306f863c785c5333a9ecc091248c6ee68b4b6c87fdedfb", size = 60256, upload-time = "2026-10-06T17:32:33.506Z" }, +] + +[[package]] +name = "opentelemetry-exporter-http-transport" +version = "0.66b1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/62/0c/e3ebdb4b507f66afcc905e6885a4946969bd75b45988492643356fbbdc63/opentelemetry_exporter_http_transport-0.66b1.tar.gz", hash = "sha256:443080203bf52586ce0b2ad901e8951c61833eab1aa539ae6f1f16fe9e8e7952", size = 11693, upload-time = "2026-10-06T17:32:59.65Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/69/6af86ff66492b481c6a4c05dcfd68beb47ed8ba046440a26a2aac76b95c7/opentelemetry_exporter_http_transport-0.66b1-py3-none-any.whl", hash = "sha256:2f95404bdee7f9d2d529c7de56c7bd86d014d774d8fbf137810e0167f8a492bf", size = 12155, upload-time = "2026-10-06T17:32:35.454Z" }, +] + +[package.optional-dependencies] +requests = [ + { name = "requests" }, +] + +[[package]] +name = "opentelemetry-exporter-otlp-common" +version = "0.66b1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-sdk" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cb/19/41de712173f43057e4532d42ece7d0c6d4210d353e5752433cb14987643f/opentelemetry_exporter_otlp_common-0.66b1.tar.gz", hash = "sha256:6b1403487a2185ac1feb45fd5546fdf8630ce71c36bcefaadf51e2130e9e23f9", size = 14325, upload-time = "2026-10-06T17:33:01.725Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5f/bf/93795954016c522008da367da292adceed71cca6ee1717e1d64c83089099/opentelemetry_api-1.40.0-py3-none-any.whl", hash = "sha256:82dd69331ae74b06f6a874704be0cfaa49a1650e1537d4a813b86ecef7d0ecf9", size = 68676, upload-time = "2026-03-04T14:17:01.24Z" }, + { url = "https://files.pythonhosted.org/packages/fc/39/8c23d67665c762aa51840fa06f86e902e8f6f1693bc8d7e3d98cd6e2f753/opentelemetry_exporter_otlp_common-0.66b1-py3-none-any.whl", hash = "sha256:00ff8592c3a7cb729ff3fdc7ffa12372c243bdf2163e80c180994d0c7bd83ee9", size = 12385, upload-time = "2026-10-06T17:32:38.177Z" }, ] [[package]] name = "opentelemetry-exporter-otlp-proto-common" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-proto" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/51/bc/1559d46557fe6eca0b46c88d4c2676285f1f3be2e8d06bb5d15fbffc814a/opentelemetry_exporter_otlp_proto_common-1.40.0.tar.gz", hash = "sha256:1cbee86a4064790b362a86601ee7934f368b81cd4cc2f2e163902a6e7818a0fa", size = 20416, upload-time = "2026-03-04T14:17:23.801Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c1/8e/65e85e5137991a3c493b11682151d198638a5bc1dd4b4c5f67e013c57d7c/opentelemetry_exporter_otlp_proto_common-1.45.1.tar.gz", hash = "sha256:2e4adcc3a67bcf57804fc49514f0ef64974ca7590aa3491da389852b4a0628f6", size = 18873, upload-time = "2026-10-06T17:33:04.471Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8b/ca/8f122055c97a932311a3f640273f084e738008933503d0c2563cd5d591fc/opentelemetry_exporter_otlp_proto_common-1.40.0-py3-none-any.whl", hash = "sha256:7081ff453835a82417bf38dccf122c827c3cbc94f2079b03bba02a3165f25149", size = 18369, upload-time = "2026-03-04T14:17:04.796Z" }, + { url = "https://files.pythonhosted.org/packages/84/aa/92f225d353904e7f70b8b3e3c1b02db0cf56f744c2e83c581dc372e78873/opentelemetry_exporter_otlp_proto_common-1.45.1-py3-none-any.whl", hash = "sha256:2f446183ae7047b036226f1d846c41a834b0e8755ad13b51a51dd38952eb466c", size = 15393, upload-time = "2026-10-06T17:32:41.911Z" }, ] [[package]] name = "opentelemetry-exporter-otlp-proto-http" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "googleapis-common-protos" }, { name = "opentelemetry-api" }, + { name = "opentelemetry-exporter-http-transport", extra = ["requests"] }, + { name = "opentelemetry-exporter-otlp-common" }, { name = "opentelemetry-exporter-otlp-proto-common" }, { name = "opentelemetry-proto" }, { name = "opentelemetry-sdk" }, { name = "requests" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2e/fa/73d50e2c15c56be4d000c98e24221d494674b0cc95524e2a8cb3856d95a4/opentelemetry_exporter_otlp_proto_http-1.40.0.tar.gz", hash = "sha256:db48f5e0f33217588bbc00274a31517ba830da576e59503507c839b38fa0869c", size = 17772, upload-time = "2026-03-04T14:17:25.324Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/17/26487707ea4caa97b17e6e4b5fa72133a53512ffa2f5cf7a49ef284b29cb/opentelemetry_exporter_otlp_proto_http-1.45.1.tar.gz", hash = "sha256:45c218405ce3fd879596924b1874bf9a8f6880206d61065c5a912c8e5c297fb7", size = 28839, upload-time = "2026-10-06T17:33:05.713Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/3a/8865d6754e61c9fb170cdd530a124a53769ee5f740236064816eb0ca7301/opentelemetry_exporter_otlp_proto_http-1.40.0-py3-none-any.whl", hash = "sha256:a8d1dab28f504c5d96577d6509f80a8150e44e8f45f82cdbe0e34c99ab040069", size = 19960, upload-time = "2026-03-04T14:17:07.153Z" }, + { url = "https://files.pythonhosted.org/packages/aa/1f/517eaa0187ba106a9da97160ce2add3a371812681dc440930b267f714e42/opentelemetry_exporter_otlp_proto_http-1.45.1-py3-none-any.whl", hash = "sha256:24a97cf3753c7fb52fad44a696e452ff371686339e2acf3309e2eda3d0230700", size = 22180, upload-time = "2026-10-06T17:32:43.946Z" }, ] [[package]] name = "opentelemetry-instrumentation" -version = "0.61b0" +version = "0.66b1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, @@ -443,9 +461,9 @@ dependencies = [ { name = "packaging" }, { name = "wrapt" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/da/37/6bf8e66bfcee5d3c6515b79cb2ee9ad05fe573c20f7ceb288d0e7eeec28c/opentelemetry_instrumentation-0.61b0.tar.gz", hash = "sha256:cb21b48db738c9de196eba6b805b4ff9de3b7f187e4bbf9a466fa170514f1fc7", size = 32606, upload-time = "2026-03-04T14:20:16.825Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a5/03/89e47ff8d52a4f83b343e6eb9ef1698ff45357216e5b6b2b21e0da5c5c7d/opentelemetry_instrumentation-0.66b1.tar.gz", hash = "sha256:e79a510f7d87c72d95e964ddb42193a0d9a75668c027d980eab032ea1322a5ce", size = 43308, upload-time = "2026-10-06T17:36:10.703Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d8/3e/f6f10f178b6316de67f0dfdbbb699a24fbe8917cf1743c1595fb9dcdd461/opentelemetry_instrumentation-0.61b0-py3-none-any.whl", hash = "sha256:92a93a280e69788e8f88391247cc530fd81f16f2b011979d4d6398f805cfbc63", size = 33448, upload-time = "2026-03-04T14:19:02.447Z" }, + { url = "https://files.pythonhosted.org/packages/da/b2/d1413681ff43e13ac9860df27e1226d3199ab0b97b352ceea41abcc660a5/opentelemetry_instrumentation-0.66b1-py3-none-any.whl", hash = "sha256:4c4aa14dc9a24a02325a9d4c42c4d0208dbb1374c2b1b8fe6c9392d59f3e1008", size = 36904, upload-time = "2026-10-06T17:35:11.663Z" }, ] [[package]] @@ -464,41 +482,41 @@ wheels = [ [[package]] name = "opentelemetry-proto" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "protobuf" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4c/77/dd38991db037fdfce45849491cb61de5ab000f49824a00230afb112a4392/opentelemetry_proto-1.40.0.tar.gz", hash = "sha256:03f639ca129ba513f5819810f5b1f42bcb371391405d99c168fe6937c62febcd", size = 45667, upload-time = "2026-03-04T14:17:31.194Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4b/7f/15f014fb195da6c2dbb6c71399b8e76824878718e94de6454038488eed28/opentelemetry_proto-1.45.1.tar.gz", hash = "sha256:79e0fb95e4616691a469439238aa9224d75779b3e108e895d1aa125ab29ca77c", size = 46488, upload-time = "2026-10-06T17:33:11.49Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b9/b2/189b2577dde745b15625b3214302605b1353436219d42b7912e77fa8dc24/opentelemetry_proto-1.40.0-py3-none-any.whl", hash = "sha256:266c4385d88923a23d63e353e9761af0f47a6ed0d486979777fe4de59dc9b25f", size = 72073, upload-time = "2026-03-04T14:17:16.673Z" }, + { url = "https://files.pythonhosted.org/packages/ab/9a/42ec8180a769516ae757e893b69736826efceac7332553915b4528a91c6d/opentelemetry_proto-1.45.1-py3-none-any.whl", hash = "sha256:f38e2a8413053c180cd3d2637fbb279673ec2f6a6e09c995aafa2f452c52b46e", size = 72488, upload-time = "2026-10-06T17:32:53.057Z" }, ] [[package]] name = "opentelemetry-sdk" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, { name = "opentelemetry-semantic-conventions" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/58/fd/3c3125b20ba18ce2155ba9ea74acb0ae5d25f8cd39cfd37455601b7955cc/opentelemetry_sdk-1.40.0.tar.gz", hash = "sha256:18e9f5ec20d859d268c7cb3c5198c8d105d073714db3de50b593b8c1345a48f2", size = 184252, upload-time = "2026-03-04T14:17:31.87Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/79/7392e21a1c8f0c61d90b223e31c7e48cb9d452e91a6b820ad24cca5f23c4/opentelemetry_sdk-1.45.1.tar.gz", hash = "sha256:63d24a6ca645019a631e6a51999c73e93adcac1196ca640b8ae78a7cc4762bf3", size = 218324, upload-time = "2026-10-06T17:33:13.26Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/c5/6a852903d8bfac758c6dc6e9a68b015d3c33f2f1be5e9591e0f4b69c7e0a/opentelemetry_sdk-1.40.0-py3-none-any.whl", hash = "sha256:787d2154a71f4b3d81f20524a8ce061b7db667d24e46753f32a7bc48f1c1f3f1", size = 141951, upload-time = "2026-03-04T14:17:17.961Z" }, + { url = "https://files.pythonhosted.org/packages/95/3c/87c42b4bd6dd297536f04cd9383d212ac557ecd49f2cbdcd46da1c9ef5c8/opentelemetry_sdk-1.45.1-py3-none-any.whl", hash = "sha256:c604c11dc429810812348989115fa44bd558772a3d7442afc43d024f2c250ca4", size = 140063, upload-time = "2026-10-06T17:32:55.04Z" }, ] [[package]] name = "opentelemetry-semantic-conventions" -version = "0.61b0" +version = "0.66b1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6d/c0/4ae7973f3c2cfd2b6e321f1675626f0dab0a97027cc7a297474c9c8f3d04/opentelemetry_semantic_conventions-0.61b0.tar.gz", hash = "sha256:072f65473c5d7c6dc0355b27d6c9d1a679d63b6d4b4b16a9773062cb7e31192a", size = 145755, upload-time = "2026-03-04T14:17:32.664Z" } +sdist = { url = "https://files.pythonhosted.org/packages/46/e4/dbbfb2a010c4db2224a5114638acede6fe563d33cc20fb1752cebcbe6298/opentelemetry_semantic_conventions-0.66b1.tar.gz", hash = "sha256:497ca63bf383723411e8eaf60c8779e9877633c936bb641080adab59d0eb6ec8", size = 150250, upload-time = "2026-10-06T17:33:14.073Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b2/37/cc6a55e448deaa9b27377d087da8615a3416d8ad523d5960b78dbeadd02a/opentelemetry_semantic_conventions-0.61b0-py3-none-any.whl", hash = "sha256:fa530a96be229795f8cef353739b618148b0fe2b4b3f005e60e262926c4d38e2", size = 231621, upload-time = "2026-03-04T14:17:19.33Z" }, + { url = "https://files.pythonhosted.org/packages/bc/14/67f8aa798857f8cf686f515bf93d9bb877ce952ddc8efae0fa25b45ce0d6/opentelemetry_semantic_conventions-0.66b1-py3-none-any.whl", hash = "sha256:d4cddeb4315490b35213f55e2bdc9ac54bb1e4d318927475bed62b35545e581b", size = 206279, upload-time = "2026-10-06T17:32:56.103Z" }, ] [[package]] @@ -512,7 +530,7 @@ wheels = [ [[package]] name = "posthog" -version = "7.75.4" +version = "7.75.11" source = { directory = "../../" } dependencies = [ { name = "backoff" }, @@ -877,12 +895,3 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/46/78/10ad9781128ed2f99dbc474f43283b13fea8ba58723e98844367531c18e9/wrapt-1.17.3-cp314-cp314t-win_arm64.whl", hash = "sha256:f38e60678850c42461d4202739f9bf1e3a737c7ad283638251e79cc49effb6b6", size = 38471, upload-time = "2025-08-12T05:52:57.784Z" }, { url = "https://files.pythonhosted.org/packages/1f/f6/a933bd70f98e9cf3e08167fc5cd7aaaca49147e48411c0bd5ae701bb2194/wrapt-1.17.3-py3-none-any.whl", hash = "sha256:7171ae35d2c33d326ac19dd8facb1e82e5fd04ef8c6c0e394d7af55a55051c22", size = 23591, upload-time = "2025-08-12T05:53:20.674Z" }, ] - -[[package]] -name = "zipp" -version = "3.23.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166", size = 25547, upload-time = "2025-06-08T17:06:39.4Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276, upload-time = "2025-06-08T17:06:38.034Z" }, -] diff --git a/examples/example-ai-portkey/uv.lock b/examples/example-ai-portkey/uv.lock index 77d4a659..e609b066 100644 --- a/examples/example-ai-portkey/uv.lock +++ b/examples/example-ai-portkey/uv.lock @@ -273,18 +273,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/57/b0/0e52c878c53f245edd3a11020f20979b3f490f245af532c7cae3027754b5/idna-3.19-py3-none-any.whl", hash = "sha256:815e7be7a7806d54abb586dc943addc79e8b2ee16915059658cbeff4b1b43bf4", size = 68550, upload-time = "2026-08-18T05:14:22.343Z" }, ] -[[package]] -name = "importlib-metadata" -version = "8.7.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "zipp" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/f3/49/3b30cad09e7771a4982d9975a8cbf64f00d4a1ececb53297f1d9a7be1b10/importlib_metadata-8.7.1.tar.gz", hash = "sha256:49fef1ae6440c182052f407c8d34a68f72efc36db9ca90dc0113398f2fdde8bb", size = 57107, upload-time = "2025-12-21T10:00:19.278Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fa/5e/f8e9a1d23b9c20a551a8a02ea3637b4642e22c2626e3a13a9a29cdea99eb/importlib_metadata-8.7.1-py3-none-any.whl", hash = "sha256:5a1f80bf1daa489495071efbb095d75a634cf28a8bc299581244063b53176151", size = 27865, upload-time = "2025-12-21T10:00:18.329Z" }, -] - [[package]] name = "jiter" version = "0.13.0" @@ -403,50 +391,80 @@ wheels = [ [[package]] name = "opentelemetry-api" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "importlib-metadata" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2c/1d/4049a9e8698361cc1a1aa03a6c59e4fa4c71e0c0f94a30f988a6876a2ae6/opentelemetry_api-1.40.0.tar.gz", hash = "sha256:159be641c0b04d11e9ecd576906462773eb97ae1b657730f0ecf64d32071569f", size = 70851, upload-time = "2026-03-04T14:17:21.555Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2e/02/6e0ae9cc61bd3169d401077b507b3ebc344745171e1051ab430be012dcd9/opentelemetry_api-1.45.1.tar.gz", hash = "sha256:aa38ed19bcc084ba42782a73255b3582283eced7ad6dddbd6695189e69adfb75", size = 72804, upload-time = "2026-10-06T17:32:58.133Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/41/f7dcf80b81ee8e71c1a2b59f14208bc723edbd89ed027a73b175abf6348e/opentelemetry_api-1.45.1-py3-none-any.whl", hash = "sha256:b31553efa588ae44bc306f863c785c5333a9ecc091248c6ee68b4b6c87fdedfb", size = 60256, upload-time = "2026-10-06T17:32:33.506Z" }, +] + +[[package]] +name = "opentelemetry-exporter-http-transport" +version = "0.66b1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/62/0c/e3ebdb4b507f66afcc905e6885a4946969bd75b45988492643356fbbdc63/opentelemetry_exporter_http_transport-0.66b1.tar.gz", hash = "sha256:443080203bf52586ce0b2ad901e8951c61833eab1aa539ae6f1f16fe9e8e7952", size = 11693, upload-time = "2026-10-06T17:32:59.65Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/69/6af86ff66492b481c6a4c05dcfd68beb47ed8ba046440a26a2aac76b95c7/opentelemetry_exporter_http_transport-0.66b1-py3-none-any.whl", hash = "sha256:2f95404bdee7f9d2d529c7de56c7bd86d014d774d8fbf137810e0167f8a492bf", size = 12155, upload-time = "2026-10-06T17:32:35.454Z" }, +] + +[package.optional-dependencies] +requests = [ + { name = "requests" }, +] + +[[package]] +name = "opentelemetry-exporter-otlp-common" +version = "0.66b1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-sdk" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cb/19/41de712173f43057e4532d42ece7d0c6d4210d353e5752433cb14987643f/opentelemetry_exporter_otlp_common-0.66b1.tar.gz", hash = "sha256:6b1403487a2185ac1feb45fd5546fdf8630ce71c36bcefaadf51e2130e9e23f9", size = 14325, upload-time = "2026-10-06T17:33:01.725Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5f/bf/93795954016c522008da367da292adceed71cca6ee1717e1d64c83089099/opentelemetry_api-1.40.0-py3-none-any.whl", hash = "sha256:82dd69331ae74b06f6a874704be0cfaa49a1650e1537d4a813b86ecef7d0ecf9", size = 68676, upload-time = "2026-03-04T14:17:01.24Z" }, + { url = "https://files.pythonhosted.org/packages/fc/39/8c23d67665c762aa51840fa06f86e902e8f6f1693bc8d7e3d98cd6e2f753/opentelemetry_exporter_otlp_common-0.66b1-py3-none-any.whl", hash = "sha256:00ff8592c3a7cb729ff3fdc7ffa12372c243bdf2163e80c180994d0c7bd83ee9", size = 12385, upload-time = "2026-10-06T17:32:38.177Z" }, ] [[package]] name = "opentelemetry-exporter-otlp-proto-common" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-proto" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/51/bc/1559d46557fe6eca0b46c88d4c2676285f1f3be2e8d06bb5d15fbffc814a/opentelemetry_exporter_otlp_proto_common-1.40.0.tar.gz", hash = "sha256:1cbee86a4064790b362a86601ee7934f368b81cd4cc2f2e163902a6e7818a0fa", size = 20416, upload-time = "2026-03-04T14:17:23.801Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c1/8e/65e85e5137991a3c493b11682151d198638a5bc1dd4b4c5f67e013c57d7c/opentelemetry_exporter_otlp_proto_common-1.45.1.tar.gz", hash = "sha256:2e4adcc3a67bcf57804fc49514f0ef64974ca7590aa3491da389852b4a0628f6", size = 18873, upload-time = "2026-10-06T17:33:04.471Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8b/ca/8f122055c97a932311a3f640273f084e738008933503d0c2563cd5d591fc/opentelemetry_exporter_otlp_proto_common-1.40.0-py3-none-any.whl", hash = "sha256:7081ff453835a82417bf38dccf122c827c3cbc94f2079b03bba02a3165f25149", size = 18369, upload-time = "2026-03-04T14:17:04.796Z" }, + { url = "https://files.pythonhosted.org/packages/84/aa/92f225d353904e7f70b8b3e3c1b02db0cf56f744c2e83c581dc372e78873/opentelemetry_exporter_otlp_proto_common-1.45.1-py3-none-any.whl", hash = "sha256:2f446183ae7047b036226f1d846c41a834b0e8755ad13b51a51dd38952eb466c", size = 15393, upload-time = "2026-10-06T17:32:41.911Z" }, ] [[package]] name = "opentelemetry-exporter-otlp-proto-http" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "googleapis-common-protos" }, { name = "opentelemetry-api" }, + { name = "opentelemetry-exporter-http-transport", extra = ["requests"] }, + { name = "opentelemetry-exporter-otlp-common" }, { name = "opentelemetry-exporter-otlp-proto-common" }, { name = "opentelemetry-proto" }, { name = "opentelemetry-sdk" }, { name = "requests" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2e/fa/73d50e2c15c56be4d000c98e24221d494674b0cc95524e2a8cb3856d95a4/opentelemetry_exporter_otlp_proto_http-1.40.0.tar.gz", hash = "sha256:db48f5e0f33217588bbc00274a31517ba830da576e59503507c839b38fa0869c", size = 17772, upload-time = "2026-03-04T14:17:25.324Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/17/26487707ea4caa97b17e6e4b5fa72133a53512ffa2f5cf7a49ef284b29cb/opentelemetry_exporter_otlp_proto_http-1.45.1.tar.gz", hash = "sha256:45c218405ce3fd879596924b1874bf9a8f6880206d61065c5a912c8e5c297fb7", size = 28839, upload-time = "2026-10-06T17:33:05.713Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/3a/8865d6754e61c9fb170cdd530a124a53769ee5f740236064816eb0ca7301/opentelemetry_exporter_otlp_proto_http-1.40.0-py3-none-any.whl", hash = "sha256:a8d1dab28f504c5d96577d6509f80a8150e44e8f45f82cdbe0e34c99ab040069", size = 19960, upload-time = "2026-03-04T14:17:07.153Z" }, + { url = "https://files.pythonhosted.org/packages/aa/1f/517eaa0187ba106a9da97160ce2add3a371812681dc440930b267f714e42/opentelemetry_exporter_otlp_proto_http-1.45.1-py3-none-any.whl", hash = "sha256:24a97cf3753c7fb52fad44a696e452ff371686339e2acf3309e2eda3d0230700", size = 22180, upload-time = "2026-10-06T17:32:43.946Z" }, ] [[package]] name = "opentelemetry-instrumentation" -version = "0.61b0" +version = "0.66b1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, @@ -454,9 +472,9 @@ dependencies = [ { name = "packaging" }, { name = "wrapt" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/da/37/6bf8e66bfcee5d3c6515b79cb2ee9ad05fe573c20f7ceb288d0e7eeec28c/opentelemetry_instrumentation-0.61b0.tar.gz", hash = "sha256:cb21b48db738c9de196eba6b805b4ff9de3b7f187e4bbf9a466fa170514f1fc7", size = 32606, upload-time = "2026-03-04T14:20:16.825Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a5/03/89e47ff8d52a4f83b343e6eb9ef1698ff45357216e5b6b2b21e0da5c5c7d/opentelemetry_instrumentation-0.66b1.tar.gz", hash = "sha256:e79a510f7d87c72d95e964ddb42193a0d9a75668c027d980eab032ea1322a5ce", size = 43308, upload-time = "2026-10-06T17:36:10.703Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d8/3e/f6f10f178b6316de67f0dfdbbb699a24fbe8917cf1743c1595fb9dcdd461/opentelemetry_instrumentation-0.61b0-py3-none-any.whl", hash = "sha256:92a93a280e69788e8f88391247cc530fd81f16f2b011979d4d6398f805cfbc63", size = 33448, upload-time = "2026-03-04T14:19:02.447Z" }, + { url = "https://files.pythonhosted.org/packages/da/b2/d1413681ff43e13ac9860df27e1226d3199ab0b97b352ceea41abcc660a5/opentelemetry_instrumentation-0.66b1-py3-none-any.whl", hash = "sha256:4c4aa14dc9a24a02325a9d4c42c4d0208dbb1374c2b1b8fe6c9392d59f3e1008", size = 36904, upload-time = "2026-10-06T17:35:11.663Z" }, ] [[package]] @@ -475,41 +493,41 @@ wheels = [ [[package]] name = "opentelemetry-proto" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "protobuf" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4c/77/dd38991db037fdfce45849491cb61de5ab000f49824a00230afb112a4392/opentelemetry_proto-1.40.0.tar.gz", hash = "sha256:03f639ca129ba513f5819810f5b1f42bcb371391405d99c168fe6937c62febcd", size = 45667, upload-time = "2026-03-04T14:17:31.194Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4b/7f/15f014fb195da6c2dbb6c71399b8e76824878718e94de6454038488eed28/opentelemetry_proto-1.45.1.tar.gz", hash = "sha256:79e0fb95e4616691a469439238aa9224d75779b3e108e895d1aa125ab29ca77c", size = 46488, upload-time = "2026-10-06T17:33:11.49Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b9/b2/189b2577dde745b15625b3214302605b1353436219d42b7912e77fa8dc24/opentelemetry_proto-1.40.0-py3-none-any.whl", hash = "sha256:266c4385d88923a23d63e353e9761af0f47a6ed0d486979777fe4de59dc9b25f", size = 72073, upload-time = "2026-03-04T14:17:16.673Z" }, + { url = "https://files.pythonhosted.org/packages/ab/9a/42ec8180a769516ae757e893b69736826efceac7332553915b4528a91c6d/opentelemetry_proto-1.45.1-py3-none-any.whl", hash = "sha256:f38e2a8413053c180cd3d2637fbb279673ec2f6a6e09c995aafa2f452c52b46e", size = 72488, upload-time = "2026-10-06T17:32:53.057Z" }, ] [[package]] name = "opentelemetry-sdk" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, { name = "opentelemetry-semantic-conventions" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/58/fd/3c3125b20ba18ce2155ba9ea74acb0ae5d25f8cd39cfd37455601b7955cc/opentelemetry_sdk-1.40.0.tar.gz", hash = "sha256:18e9f5ec20d859d268c7cb3c5198c8d105d073714db3de50b593b8c1345a48f2", size = 184252, upload-time = "2026-03-04T14:17:31.87Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/79/7392e21a1c8f0c61d90b223e31c7e48cb9d452e91a6b820ad24cca5f23c4/opentelemetry_sdk-1.45.1.tar.gz", hash = "sha256:63d24a6ca645019a631e6a51999c73e93adcac1196ca640b8ae78a7cc4762bf3", size = 218324, upload-time = "2026-10-06T17:33:13.26Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/c5/6a852903d8bfac758c6dc6e9a68b015d3c33f2f1be5e9591e0f4b69c7e0a/opentelemetry_sdk-1.40.0-py3-none-any.whl", hash = "sha256:787d2154a71f4b3d81f20524a8ce061b7db667d24e46753f32a7bc48f1c1f3f1", size = 141951, upload-time = "2026-03-04T14:17:17.961Z" }, + { url = "https://files.pythonhosted.org/packages/95/3c/87c42b4bd6dd297536f04cd9383d212ac557ecd49f2cbdcd46da1c9ef5c8/opentelemetry_sdk-1.45.1-py3-none-any.whl", hash = "sha256:c604c11dc429810812348989115fa44bd558772a3d7442afc43d024f2c250ca4", size = 140063, upload-time = "2026-10-06T17:32:55.04Z" }, ] [[package]] name = "opentelemetry-semantic-conventions" -version = "0.61b0" +version = "0.66b1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6d/c0/4ae7973f3c2cfd2b6e321f1675626f0dab0a97027cc7a297474c9c8f3d04/opentelemetry_semantic_conventions-0.61b0.tar.gz", hash = "sha256:072f65473c5d7c6dc0355b27d6c9d1a679d63b6d4b4b16a9773062cb7e31192a", size = 145755, upload-time = "2026-03-04T14:17:32.664Z" } +sdist = { url = "https://files.pythonhosted.org/packages/46/e4/dbbfb2a010c4db2224a5114638acede6fe563d33cc20fb1752cebcbe6298/opentelemetry_semantic_conventions-0.66b1.tar.gz", hash = "sha256:497ca63bf383723411e8eaf60c8779e9877633c936bb641080adab59d0eb6ec8", size = 150250, upload-time = "2026-10-06T17:33:14.073Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b2/37/cc6a55e448deaa9b27377d087da8615a3416d8ad523d5960b78dbeadd02a/opentelemetry_semantic_conventions-0.61b0-py3-none-any.whl", hash = "sha256:fa530a96be229795f8cef353739b618148b0fe2b4b3f005e60e262926c4d38e2", size = 231621, upload-time = "2026-03-04T14:17:19.33Z" }, + { url = "https://files.pythonhosted.org/packages/bc/14/67f8aa798857f8cf686f515bf93d9bb877ce952ddc8efae0fa25b45ce0d6/opentelemetry_semantic_conventions-0.66b1-py3-none-any.whl", hash = "sha256:d4cddeb4315490b35213f55e2bdc9ac54bb1e4d318927475bed62b35545e581b", size = 206279, upload-time = "2026-10-06T17:32:56.103Z" }, ] [[package]] @@ -544,7 +562,7 @@ wheels = [ [[package]] name = "posthog" -version = "7.75.4" +version = "7.75.11" source = { directory = "../../" } dependencies = [ { name = "backoff" }, @@ -921,12 +939,3 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/46/78/10ad9781128ed2f99dbc474f43283b13fea8ba58723e98844367531c18e9/wrapt-1.17.3-cp314-cp314t-win_arm64.whl", hash = "sha256:f38e60678850c42461d4202739f9bf1e3a737c7ad283638251e79cc49effb6b6", size = 38471, upload-time = "2025-08-12T05:52:57.784Z" }, { url = "https://files.pythonhosted.org/packages/1f/f6/a933bd70f98e9cf3e08167fc5cd7aaaca49147e48411c0bd5ae701bb2194/wrapt-1.17.3-py3-none-any.whl", hash = "sha256:7171ae35d2c33d326ac19dd8facb1e82e5fd04ef8c6c0e394d7af55a55051c22", size = 23591, upload-time = "2025-08-12T05:53:20.674Z" }, ] - -[[package]] -name = "zipp" -version = "3.23.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166", size = 25547, upload-time = "2025-06-08T17:06:39.4Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276, upload-time = "2025-06-08T17:06:38.034Z" }, -] diff --git a/examples/example-ai-semantic-kernel/uv.lock b/examples/example-ai-semantic-kernel/uv.lock index 293cbf9d..b19ee095 100644 --- a/examples/example-ai-semantic-kernel/uv.lock +++ b/examples/example-ai-semantic-kernel/uv.lock @@ -1091,18 +1091,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/9c/1f/19ebc343cc71a7ffa78f17018535adc5cbdd87afb31d7c34874680148b32/ifaddr-0.2.0-py3-none-any.whl", hash = "sha256:085e0305cfe6f16ab12d72e2024030f5d52674afad6911bb1eee207177b8a748", size = 12314, upload-time = "2022-06-15T21:40:25.756Z" }, ] -[[package]] -name = "importlib-metadata" -version = "8.7.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "zipp" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/f3/49/3b30cad09e7771a4982d9975a8cbf64f00d4a1ececb53297f1d9a7be1b10/importlib_metadata-8.7.1.tar.gz", hash = "sha256:49fef1ae6440c182052f407c8d34a68f72efc36db9ca90dc0113398f2fdde8bb", size = 57107, upload-time = "2025-12-21T10:00:19.278Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fa/5e/f8e9a1d23b9c20a551a8a02ea3637b4642e22c2626e3a13a9a29cdea99eb/importlib_metadata-8.7.1-py3-none-any.whl", hash = "sha256:5a1f80bf1daa489495071efbb095d75a634cf28a8bc299581244063b53176151", size = 27865, upload-time = "2025-12-21T10:00:18.329Z" }, -] - [[package]] name = "isodate" version = "0.7.2" @@ -1876,50 +1864,80 @@ wheels = [ [[package]] name = "opentelemetry-api" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "importlib-metadata" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2c/1d/4049a9e8698361cc1a1aa03a6c59e4fa4c71e0c0f94a30f988a6876a2ae6/opentelemetry_api-1.40.0.tar.gz", hash = "sha256:159be641c0b04d11e9ecd576906462773eb97ae1b657730f0ecf64d32071569f", size = 70851, upload-time = "2026-03-04T14:17:21.555Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2e/02/6e0ae9cc61bd3169d401077b507b3ebc344745171e1051ab430be012dcd9/opentelemetry_api-1.45.1.tar.gz", hash = "sha256:aa38ed19bcc084ba42782a73255b3582283eced7ad6dddbd6695189e69adfb75", size = 72804, upload-time = "2026-10-06T17:32:58.133Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/41/f7dcf80b81ee8e71c1a2b59f14208bc723edbd89ed027a73b175abf6348e/opentelemetry_api-1.45.1-py3-none-any.whl", hash = "sha256:b31553efa588ae44bc306f863c785c5333a9ecc091248c6ee68b4b6c87fdedfb", size = 60256, upload-time = "2026-10-06T17:32:33.506Z" }, +] + +[[package]] +name = "opentelemetry-exporter-http-transport" +version = "0.66b1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/62/0c/e3ebdb4b507f66afcc905e6885a4946969bd75b45988492643356fbbdc63/opentelemetry_exporter_http_transport-0.66b1.tar.gz", hash = "sha256:443080203bf52586ce0b2ad901e8951c61833eab1aa539ae6f1f16fe9e8e7952", size = 11693, upload-time = "2026-10-06T17:32:59.65Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/69/6af86ff66492b481c6a4c05dcfd68beb47ed8ba046440a26a2aac76b95c7/opentelemetry_exporter_http_transport-0.66b1-py3-none-any.whl", hash = "sha256:2f95404bdee7f9d2d529c7de56c7bd86d014d774d8fbf137810e0167f8a492bf", size = 12155, upload-time = "2026-10-06T17:32:35.454Z" }, +] + +[package.optional-dependencies] +requests = [ + { name = "requests" }, +] + +[[package]] +name = "opentelemetry-exporter-otlp-common" +version = "0.66b1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-sdk" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cb/19/41de712173f43057e4532d42ece7d0c6d4210d353e5752433cb14987643f/opentelemetry_exporter_otlp_common-0.66b1.tar.gz", hash = "sha256:6b1403487a2185ac1feb45fd5546fdf8630ce71c36bcefaadf51e2130e9e23f9", size = 14325, upload-time = "2026-10-06T17:33:01.725Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5f/bf/93795954016c522008da367da292adceed71cca6ee1717e1d64c83089099/opentelemetry_api-1.40.0-py3-none-any.whl", hash = "sha256:82dd69331ae74b06f6a874704be0cfaa49a1650e1537d4a813b86ecef7d0ecf9", size = 68676, upload-time = "2026-03-04T14:17:01.24Z" }, + { url = "https://files.pythonhosted.org/packages/fc/39/8c23d67665c762aa51840fa06f86e902e8f6f1693bc8d7e3d98cd6e2f753/opentelemetry_exporter_otlp_common-0.66b1-py3-none-any.whl", hash = "sha256:00ff8592c3a7cb729ff3fdc7ffa12372c243bdf2163e80c180994d0c7bd83ee9", size = 12385, upload-time = "2026-10-06T17:32:38.177Z" }, ] [[package]] name = "opentelemetry-exporter-otlp-proto-common" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-proto" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/51/bc/1559d46557fe6eca0b46c88d4c2676285f1f3be2e8d06bb5d15fbffc814a/opentelemetry_exporter_otlp_proto_common-1.40.0.tar.gz", hash = "sha256:1cbee86a4064790b362a86601ee7934f368b81cd4cc2f2e163902a6e7818a0fa", size = 20416, upload-time = "2026-03-04T14:17:23.801Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c1/8e/65e85e5137991a3c493b11682151d198638a5bc1dd4b4c5f67e013c57d7c/opentelemetry_exporter_otlp_proto_common-1.45.1.tar.gz", hash = "sha256:2e4adcc3a67bcf57804fc49514f0ef64974ca7590aa3491da389852b4a0628f6", size = 18873, upload-time = "2026-10-06T17:33:04.471Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8b/ca/8f122055c97a932311a3f640273f084e738008933503d0c2563cd5d591fc/opentelemetry_exporter_otlp_proto_common-1.40.0-py3-none-any.whl", hash = "sha256:7081ff453835a82417bf38dccf122c827c3cbc94f2079b03bba02a3165f25149", size = 18369, upload-time = "2026-03-04T14:17:04.796Z" }, + { url = "https://files.pythonhosted.org/packages/84/aa/92f225d353904e7f70b8b3e3c1b02db0cf56f744c2e83c581dc372e78873/opentelemetry_exporter_otlp_proto_common-1.45.1-py3-none-any.whl", hash = "sha256:2f446183ae7047b036226f1d846c41a834b0e8755ad13b51a51dd38952eb466c", size = 15393, upload-time = "2026-10-06T17:32:41.911Z" }, ] [[package]] name = "opentelemetry-exporter-otlp-proto-http" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "googleapis-common-protos" }, { name = "opentelemetry-api" }, + { name = "opentelemetry-exporter-http-transport", extra = ["requests"] }, + { name = "opentelemetry-exporter-otlp-common" }, { name = "opentelemetry-exporter-otlp-proto-common" }, { name = "opentelemetry-proto" }, { name = "opentelemetry-sdk" }, { name = "requests" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2e/fa/73d50e2c15c56be4d000c98e24221d494674b0cc95524e2a8cb3856d95a4/opentelemetry_exporter_otlp_proto_http-1.40.0.tar.gz", hash = "sha256:db48f5e0f33217588bbc00274a31517ba830da576e59503507c839b38fa0869c", size = 17772, upload-time = "2026-03-04T14:17:25.324Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/17/26487707ea4caa97b17e6e4b5fa72133a53512ffa2f5cf7a49ef284b29cb/opentelemetry_exporter_otlp_proto_http-1.45.1.tar.gz", hash = "sha256:45c218405ce3fd879596924b1874bf9a8f6880206d61065c5a912c8e5c297fb7", size = 28839, upload-time = "2026-10-06T17:33:05.713Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/3a/8865d6754e61c9fb170cdd530a124a53769ee5f740236064816eb0ca7301/opentelemetry_exporter_otlp_proto_http-1.40.0-py3-none-any.whl", hash = "sha256:a8d1dab28f504c5d96577d6509f80a8150e44e8f45f82cdbe0e34c99ab040069", size = 19960, upload-time = "2026-03-04T14:17:07.153Z" }, + { url = "https://files.pythonhosted.org/packages/aa/1f/517eaa0187ba106a9da97160ce2add3a371812681dc440930b267f714e42/opentelemetry_exporter_otlp_proto_http-1.45.1-py3-none-any.whl", hash = "sha256:24a97cf3753c7fb52fad44a696e452ff371686339e2acf3309e2eda3d0230700", size = 22180, upload-time = "2026-10-06T17:32:43.946Z" }, ] [[package]] name = "opentelemetry-instrumentation" -version = "0.61b0" +version = "0.66b1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, @@ -1927,9 +1945,9 @@ dependencies = [ { name = "packaging" }, { name = "wrapt" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/da/37/6bf8e66bfcee5d3c6515b79cb2ee9ad05fe573c20f7ceb288d0e7eeec28c/opentelemetry_instrumentation-0.61b0.tar.gz", hash = "sha256:cb21b48db738c9de196eba6b805b4ff9de3b7f187e4bbf9a466fa170514f1fc7", size = 32606, upload-time = "2026-03-04T14:20:16.825Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a5/03/89e47ff8d52a4f83b343e6eb9ef1698ff45357216e5b6b2b21e0da5c5c7d/opentelemetry_instrumentation-0.66b1.tar.gz", hash = "sha256:e79a510f7d87c72d95e964ddb42193a0d9a75668c027d980eab032ea1322a5ce", size = 43308, upload-time = "2026-10-06T17:36:10.703Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d8/3e/f6f10f178b6316de67f0dfdbbb699a24fbe8917cf1743c1595fb9dcdd461/opentelemetry_instrumentation-0.61b0-py3-none-any.whl", hash = "sha256:92a93a280e69788e8f88391247cc530fd81f16f2b011979d4d6398f805cfbc63", size = 33448, upload-time = "2026-03-04T14:19:02.447Z" }, + { url = "https://files.pythonhosted.org/packages/da/b2/d1413681ff43e13ac9860df27e1226d3199ab0b97b352ceea41abcc660a5/opentelemetry_instrumentation-0.66b1-py3-none-any.whl", hash = "sha256:4c4aa14dc9a24a02325a9d4c42c4d0208dbb1374c2b1b8fe6c9392d59f3e1008", size = 36904, upload-time = "2026-10-06T17:35:11.663Z" }, ] [[package]] @@ -1948,41 +1966,41 @@ wheels = [ [[package]] name = "opentelemetry-proto" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "protobuf" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4c/77/dd38991db037fdfce45849491cb61de5ab000f49824a00230afb112a4392/opentelemetry_proto-1.40.0.tar.gz", hash = "sha256:03f639ca129ba513f5819810f5b1f42bcb371391405d99c168fe6937c62febcd", size = 45667, upload-time = "2026-03-04T14:17:31.194Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4b/7f/15f014fb195da6c2dbb6c71399b8e76824878718e94de6454038488eed28/opentelemetry_proto-1.45.1.tar.gz", hash = "sha256:79e0fb95e4616691a469439238aa9224d75779b3e108e895d1aa125ab29ca77c", size = 46488, upload-time = "2026-10-06T17:33:11.49Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b9/b2/189b2577dde745b15625b3214302605b1353436219d42b7912e77fa8dc24/opentelemetry_proto-1.40.0-py3-none-any.whl", hash = "sha256:266c4385d88923a23d63e353e9761af0f47a6ed0d486979777fe4de59dc9b25f", size = 72073, upload-time = "2026-03-04T14:17:16.673Z" }, + { url = "https://files.pythonhosted.org/packages/ab/9a/42ec8180a769516ae757e893b69736826efceac7332553915b4528a91c6d/opentelemetry_proto-1.45.1-py3-none-any.whl", hash = "sha256:f38e2a8413053c180cd3d2637fbb279673ec2f6a6e09c995aafa2f452c52b46e", size = 72488, upload-time = "2026-10-06T17:32:53.057Z" }, ] [[package]] name = "opentelemetry-sdk" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, { name = "opentelemetry-semantic-conventions" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/58/fd/3c3125b20ba18ce2155ba9ea74acb0ae5d25f8cd39cfd37455601b7955cc/opentelemetry_sdk-1.40.0.tar.gz", hash = "sha256:18e9f5ec20d859d268c7cb3c5198c8d105d073714db3de50b593b8c1345a48f2", size = 184252, upload-time = "2026-03-04T14:17:31.87Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/79/7392e21a1c8f0c61d90b223e31c7e48cb9d452e91a6b820ad24cca5f23c4/opentelemetry_sdk-1.45.1.tar.gz", hash = "sha256:63d24a6ca645019a631e6a51999c73e93adcac1196ca640b8ae78a7cc4762bf3", size = 218324, upload-time = "2026-10-06T17:33:13.26Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/c5/6a852903d8bfac758c6dc6e9a68b015d3c33f2f1be5e9591e0f4b69c7e0a/opentelemetry_sdk-1.40.0-py3-none-any.whl", hash = "sha256:787d2154a71f4b3d81f20524a8ce061b7db667d24e46753f32a7bc48f1c1f3f1", size = 141951, upload-time = "2026-03-04T14:17:17.961Z" }, + { url = "https://files.pythonhosted.org/packages/95/3c/87c42b4bd6dd297536f04cd9383d212ac557ecd49f2cbdcd46da1c9ef5c8/opentelemetry_sdk-1.45.1-py3-none-any.whl", hash = "sha256:c604c11dc429810812348989115fa44bd558772a3d7442afc43d024f2c250ca4", size = 140063, upload-time = "2026-10-06T17:32:55.04Z" }, ] [[package]] name = "opentelemetry-semantic-conventions" -version = "0.61b0" +version = "0.66b1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6d/c0/4ae7973f3c2cfd2b6e321f1675626f0dab0a97027cc7a297474c9c8f3d04/opentelemetry_semantic_conventions-0.61b0.tar.gz", hash = "sha256:072f65473c5d7c6dc0355b27d6c9d1a679d63b6d4b4b16a9773062cb7e31192a", size = 145755, upload-time = "2026-03-04T14:17:32.664Z" } +sdist = { url = "https://files.pythonhosted.org/packages/46/e4/dbbfb2a010c4db2224a5114638acede6fe563d33cc20fb1752cebcbe6298/opentelemetry_semantic_conventions-0.66b1.tar.gz", hash = "sha256:497ca63bf383723411e8eaf60c8779e9877633c936bb641080adab59d0eb6ec8", size = 150250, upload-time = "2026-10-06T17:33:14.073Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b2/37/cc6a55e448deaa9b27377d087da8615a3416d8ad523d5960b78dbeadd02a/opentelemetry_semantic_conventions-0.61b0-py3-none-any.whl", hash = "sha256:fa530a96be229795f8cef353739b618148b0fe2b4b3f005e60e262926c4d38e2", size = 231621, upload-time = "2026-03-04T14:17:19.33Z" }, + { url = "https://files.pythonhosted.org/packages/bc/14/67f8aa798857f8cf686f515bf93d9bb877ce952ddc8efae0fa25b45ce0d6/opentelemetry_semantic_conventions-0.66b1-py3-none-any.whl", hash = "sha256:d4cddeb4315490b35213f55e2bdc9ac54bb1e4d318927475bed62b35545e581b", size = 206279, upload-time = "2026-10-06T17:32:56.103Z" }, ] [[package]] @@ -2014,7 +2032,7 @@ wheels = [ [[package]] name = "posthog" -version = "7.75.4" +version = "7.75.11" source = { directory = "../../" } dependencies = [ { name = "backoff" }, @@ -3320,12 +3338,3 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/88/91/41e284ca2cf5211e05dae031d126a3668aea88fa759df56e7e35c6ad25ba/yarl-1.25.1-cp315-cp315t-win_arm64.whl", hash = "sha256:783dd1467083f4d3f7722ad6a313f24c173e7571372738fcb7a6e6d1ba48df25", size = 101804, upload-time = "2026-09-15T19:34:57.231Z" }, { url = "https://files.pythonhosted.org/packages/54/22/318c7980066769c6bcd9221ed2248294f5698811da099013098c670565ed/yarl-1.25.1-py3-none-any.whl", hash = "sha256:681c758b0490f9e96b78e5fa8e8dc6e648e9185bb6eaebe73183c33ea0c445f3", size = 63617, upload-time = "2026-09-15T19:34:59.616Z" }, ] - -[[package]] -name = "zipp" -version = "3.23.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166", size = 25547, upload-time = "2025-06-08T17:06:39.4Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276, upload-time = "2025-06-08T17:06:38.034Z" }, -] diff --git a/examples/example-ai-smolagents/uv.lock b/examples/example-ai-smolagents/uv.lock index 7cbaabb7..a35eb750 100644 --- a/examples/example-ai-smolagents/uv.lock +++ b/examples/example-ai-smolagents/uv.lock @@ -333,18 +333,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/58/a2/bb081bab032533a855d44de1d56f8e8426114ff1ba5d1f07a438a0a654f8/idna-3.20-py3-none-any.whl", hash = "sha256:ab7ae7122974553370f0bdb919e1a960b2cd1bc1ef0276416d896db81c14582c", size = 69583, upload-time = "2026-09-17T14:11:03.168Z" }, ] -[[package]] -name = "importlib-metadata" -version = "8.7.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "zipp" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/f3/49/3b30cad09e7771a4982d9975a8cbf64f00d4a1ececb53297f1d9a7be1b10/importlib_metadata-8.7.1.tar.gz", hash = "sha256:49fef1ae6440c182052f407c8d34a68f72efc36db9ca90dc0113398f2fdde8bb", size = 57107, upload-time = "2025-12-21T10:00:19.278Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fa/5e/f8e9a1d23b9c20a551a8a02ea3637b4642e22c2626e3a13a9a29cdea99eb/importlib_metadata-8.7.1-py3-none-any.whl", hash = "sha256:5a1f80bf1daa489495071efbb095d75a634cf28a8bc299581244063b53176151", size = 27865, upload-time = "2025-12-21T10:00:18.329Z" }, -] - [[package]] name = "jinja2" version = "3.1.6" @@ -581,50 +569,80 @@ wheels = [ [[package]] name = "opentelemetry-api" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "importlib-metadata" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2c/1d/4049a9e8698361cc1a1aa03a6c59e4fa4c71e0c0f94a30f988a6876a2ae6/opentelemetry_api-1.40.0.tar.gz", hash = "sha256:159be641c0b04d11e9ecd576906462773eb97ae1b657730f0ecf64d32071569f", size = 70851, upload-time = "2026-03-04T14:17:21.555Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2e/02/6e0ae9cc61bd3169d401077b507b3ebc344745171e1051ab430be012dcd9/opentelemetry_api-1.45.1.tar.gz", hash = "sha256:aa38ed19bcc084ba42782a73255b3582283eced7ad6dddbd6695189e69adfb75", size = 72804, upload-time = "2026-10-06T17:32:58.133Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/41/f7dcf80b81ee8e71c1a2b59f14208bc723edbd89ed027a73b175abf6348e/opentelemetry_api-1.45.1-py3-none-any.whl", hash = "sha256:b31553efa588ae44bc306f863c785c5333a9ecc091248c6ee68b4b6c87fdedfb", size = 60256, upload-time = "2026-10-06T17:32:33.506Z" }, +] + +[[package]] +name = "opentelemetry-exporter-http-transport" +version = "0.66b1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/62/0c/e3ebdb4b507f66afcc905e6885a4946969bd75b45988492643356fbbdc63/opentelemetry_exporter_http_transport-0.66b1.tar.gz", hash = "sha256:443080203bf52586ce0b2ad901e8951c61833eab1aa539ae6f1f16fe9e8e7952", size = 11693, upload-time = "2026-10-06T17:32:59.65Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/69/6af86ff66492b481c6a4c05dcfd68beb47ed8ba046440a26a2aac76b95c7/opentelemetry_exporter_http_transport-0.66b1-py3-none-any.whl", hash = "sha256:2f95404bdee7f9d2d529c7de56c7bd86d014d774d8fbf137810e0167f8a492bf", size = 12155, upload-time = "2026-10-06T17:32:35.454Z" }, +] + +[package.optional-dependencies] +requests = [ + { name = "requests" }, +] + +[[package]] +name = "opentelemetry-exporter-otlp-common" +version = "0.66b1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-sdk" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cb/19/41de712173f43057e4532d42ece7d0c6d4210d353e5752433cb14987643f/opentelemetry_exporter_otlp_common-0.66b1.tar.gz", hash = "sha256:6b1403487a2185ac1feb45fd5546fdf8630ce71c36bcefaadf51e2130e9e23f9", size = 14325, upload-time = "2026-10-06T17:33:01.725Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5f/bf/93795954016c522008da367da292adceed71cca6ee1717e1d64c83089099/opentelemetry_api-1.40.0-py3-none-any.whl", hash = "sha256:82dd69331ae74b06f6a874704be0cfaa49a1650e1537d4a813b86ecef7d0ecf9", size = 68676, upload-time = "2026-03-04T14:17:01.24Z" }, + { url = "https://files.pythonhosted.org/packages/fc/39/8c23d67665c762aa51840fa06f86e902e8f6f1693bc8d7e3d98cd6e2f753/opentelemetry_exporter_otlp_common-0.66b1-py3-none-any.whl", hash = "sha256:00ff8592c3a7cb729ff3fdc7ffa12372c243bdf2163e80c180994d0c7bd83ee9", size = 12385, upload-time = "2026-10-06T17:32:38.177Z" }, ] [[package]] name = "opentelemetry-exporter-otlp-proto-common" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-proto" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/51/bc/1559d46557fe6eca0b46c88d4c2676285f1f3be2e8d06bb5d15fbffc814a/opentelemetry_exporter_otlp_proto_common-1.40.0.tar.gz", hash = "sha256:1cbee86a4064790b362a86601ee7934f368b81cd4cc2f2e163902a6e7818a0fa", size = 20416, upload-time = "2026-03-04T14:17:23.801Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c1/8e/65e85e5137991a3c493b11682151d198638a5bc1dd4b4c5f67e013c57d7c/opentelemetry_exporter_otlp_proto_common-1.45.1.tar.gz", hash = "sha256:2e4adcc3a67bcf57804fc49514f0ef64974ca7590aa3491da389852b4a0628f6", size = 18873, upload-time = "2026-10-06T17:33:04.471Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8b/ca/8f122055c97a932311a3f640273f084e738008933503d0c2563cd5d591fc/opentelemetry_exporter_otlp_proto_common-1.40.0-py3-none-any.whl", hash = "sha256:7081ff453835a82417bf38dccf122c827c3cbc94f2079b03bba02a3165f25149", size = 18369, upload-time = "2026-03-04T14:17:04.796Z" }, + { url = "https://files.pythonhosted.org/packages/84/aa/92f225d353904e7f70b8b3e3c1b02db0cf56f744c2e83c581dc372e78873/opentelemetry_exporter_otlp_proto_common-1.45.1-py3-none-any.whl", hash = "sha256:2f446183ae7047b036226f1d846c41a834b0e8755ad13b51a51dd38952eb466c", size = 15393, upload-time = "2026-10-06T17:32:41.911Z" }, ] [[package]] name = "opentelemetry-exporter-otlp-proto-http" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "googleapis-common-protos" }, { name = "opentelemetry-api" }, + { name = "opentelemetry-exporter-http-transport", extra = ["requests"] }, + { name = "opentelemetry-exporter-otlp-common" }, { name = "opentelemetry-exporter-otlp-proto-common" }, { name = "opentelemetry-proto" }, { name = "opentelemetry-sdk" }, { name = "requests" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2e/fa/73d50e2c15c56be4d000c98e24221d494674b0cc95524e2a8cb3856d95a4/opentelemetry_exporter_otlp_proto_http-1.40.0.tar.gz", hash = "sha256:db48f5e0f33217588bbc00274a31517ba830da576e59503507c839b38fa0869c", size = 17772, upload-time = "2026-03-04T14:17:25.324Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/17/26487707ea4caa97b17e6e4b5fa72133a53512ffa2f5cf7a49ef284b29cb/opentelemetry_exporter_otlp_proto_http-1.45.1.tar.gz", hash = "sha256:45c218405ce3fd879596924b1874bf9a8f6880206d61065c5a912c8e5c297fb7", size = 28839, upload-time = "2026-10-06T17:33:05.713Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/3a/8865d6754e61c9fb170cdd530a124a53769ee5f740236064816eb0ca7301/opentelemetry_exporter_otlp_proto_http-1.40.0-py3-none-any.whl", hash = "sha256:a8d1dab28f504c5d96577d6509f80a8150e44e8f45f82cdbe0e34c99ab040069", size = 19960, upload-time = "2026-03-04T14:17:07.153Z" }, + { url = "https://files.pythonhosted.org/packages/aa/1f/517eaa0187ba106a9da97160ce2add3a371812681dc440930b267f714e42/opentelemetry_exporter_otlp_proto_http-1.45.1-py3-none-any.whl", hash = "sha256:24a97cf3753c7fb52fad44a696e452ff371686339e2acf3309e2eda3d0230700", size = 22180, upload-time = "2026-10-06T17:32:43.946Z" }, ] [[package]] name = "opentelemetry-instrumentation" -version = "0.61b0" +version = "0.66b1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, @@ -632,9 +650,9 @@ dependencies = [ { name = "packaging" }, { name = "wrapt" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/da/37/6bf8e66bfcee5d3c6515b79cb2ee9ad05fe573c20f7ceb288d0e7eeec28c/opentelemetry_instrumentation-0.61b0.tar.gz", hash = "sha256:cb21b48db738c9de196eba6b805b4ff9de3b7f187e4bbf9a466fa170514f1fc7", size = 32606, upload-time = "2026-03-04T14:20:16.825Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a5/03/89e47ff8d52a4f83b343e6eb9ef1698ff45357216e5b6b2b21e0da5c5c7d/opentelemetry_instrumentation-0.66b1.tar.gz", hash = "sha256:e79a510f7d87c72d95e964ddb42193a0d9a75668c027d980eab032ea1322a5ce", size = 43308, upload-time = "2026-10-06T17:36:10.703Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d8/3e/f6f10f178b6316de67f0dfdbbb699a24fbe8917cf1743c1595fb9dcdd461/opentelemetry_instrumentation-0.61b0-py3-none-any.whl", hash = "sha256:92a93a280e69788e8f88391247cc530fd81f16f2b011979d4d6398f805cfbc63", size = 33448, upload-time = "2026-03-04T14:19:02.447Z" }, + { url = "https://files.pythonhosted.org/packages/da/b2/d1413681ff43e13ac9860df27e1226d3199ab0b97b352ceea41abcc660a5/opentelemetry_instrumentation-0.66b1-py3-none-any.whl", hash = "sha256:4c4aa14dc9a24a02325a9d4c42c4d0208dbb1374c2b1b8fe6c9392d59f3e1008", size = 36904, upload-time = "2026-10-06T17:35:11.663Z" }, ] [[package]] @@ -653,41 +671,41 @@ wheels = [ [[package]] name = "opentelemetry-proto" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "protobuf" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4c/77/dd38991db037fdfce45849491cb61de5ab000f49824a00230afb112a4392/opentelemetry_proto-1.40.0.tar.gz", hash = "sha256:03f639ca129ba513f5819810f5b1f42bcb371391405d99c168fe6937c62febcd", size = 45667, upload-time = "2026-03-04T14:17:31.194Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4b/7f/15f014fb195da6c2dbb6c71399b8e76824878718e94de6454038488eed28/opentelemetry_proto-1.45.1.tar.gz", hash = "sha256:79e0fb95e4616691a469439238aa9224d75779b3e108e895d1aa125ab29ca77c", size = 46488, upload-time = "2026-10-06T17:33:11.49Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b9/b2/189b2577dde745b15625b3214302605b1353436219d42b7912e77fa8dc24/opentelemetry_proto-1.40.0-py3-none-any.whl", hash = "sha256:266c4385d88923a23d63e353e9761af0f47a6ed0d486979777fe4de59dc9b25f", size = 72073, upload-time = "2026-03-04T14:17:16.673Z" }, + { url = "https://files.pythonhosted.org/packages/ab/9a/42ec8180a769516ae757e893b69736826efceac7332553915b4528a91c6d/opentelemetry_proto-1.45.1-py3-none-any.whl", hash = "sha256:f38e2a8413053c180cd3d2637fbb279673ec2f6a6e09c995aafa2f452c52b46e", size = 72488, upload-time = "2026-10-06T17:32:53.057Z" }, ] [[package]] name = "opentelemetry-sdk" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, { name = "opentelemetry-semantic-conventions" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/58/fd/3c3125b20ba18ce2155ba9ea74acb0ae5d25f8cd39cfd37455601b7955cc/opentelemetry_sdk-1.40.0.tar.gz", hash = "sha256:18e9f5ec20d859d268c7cb3c5198c8d105d073714db3de50b593b8c1345a48f2", size = 184252, upload-time = "2026-03-04T14:17:31.87Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/79/7392e21a1c8f0c61d90b223e31c7e48cb9d452e91a6b820ad24cca5f23c4/opentelemetry_sdk-1.45.1.tar.gz", hash = "sha256:63d24a6ca645019a631e6a51999c73e93adcac1196ca640b8ae78a7cc4762bf3", size = 218324, upload-time = "2026-10-06T17:33:13.26Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/c5/6a852903d8bfac758c6dc6e9a68b015d3c33f2f1be5e9591e0f4b69c7e0a/opentelemetry_sdk-1.40.0-py3-none-any.whl", hash = "sha256:787d2154a71f4b3d81f20524a8ce061b7db667d24e46753f32a7bc48f1c1f3f1", size = 141951, upload-time = "2026-03-04T14:17:17.961Z" }, + { url = "https://files.pythonhosted.org/packages/95/3c/87c42b4bd6dd297536f04cd9383d212ac557ecd49f2cbdcd46da1c9ef5c8/opentelemetry_sdk-1.45.1-py3-none-any.whl", hash = "sha256:c604c11dc429810812348989115fa44bd558772a3d7442afc43d024f2c250ca4", size = 140063, upload-time = "2026-10-06T17:32:55.04Z" }, ] [[package]] name = "opentelemetry-semantic-conventions" -version = "0.61b0" +version = "0.66b1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6d/c0/4ae7973f3c2cfd2b6e321f1675626f0dab0a97027cc7a297474c9c8f3d04/opentelemetry_semantic_conventions-0.61b0.tar.gz", hash = "sha256:072f65473c5d7c6dc0355b27d6c9d1a679d63b6d4b4b16a9773062cb7e31192a", size = 145755, upload-time = "2026-03-04T14:17:32.664Z" } +sdist = { url = "https://files.pythonhosted.org/packages/46/e4/dbbfb2a010c4db2224a5114638acede6fe563d33cc20fb1752cebcbe6298/opentelemetry_semantic_conventions-0.66b1.tar.gz", hash = "sha256:497ca63bf383723411e8eaf60c8779e9877633c936bb641080adab59d0eb6ec8", size = 150250, upload-time = "2026-10-06T17:33:14.073Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b2/37/cc6a55e448deaa9b27377d087da8615a3416d8ad523d5960b78dbeadd02a/opentelemetry_semantic_conventions-0.61b0-py3-none-any.whl", hash = "sha256:fa530a96be229795f8cef353739b618148b0fe2b4b3f005e60e262926c4d38e2", size = 231621, upload-time = "2026-03-04T14:17:19.33Z" }, + { url = "https://files.pythonhosted.org/packages/bc/14/67f8aa798857f8cf686f515bf93d9bb877ce952ddc8efae0fa25b45ce0d6/opentelemetry_semantic_conventions-0.66b1-py3-none-any.whl", hash = "sha256:d4cddeb4315490b35213f55e2bdc9ac54bb1e4d318927475bed62b35545e581b", size = 206279, upload-time = "2026-10-06T17:32:56.103Z" }, ] [[package]] @@ -795,7 +813,7 @@ wheels = [ [[package]] name = "posthog" -version = "7.75.4" +version = "7.75.11" source = { directory = "../../" } dependencies = [ { name = "backoff" }, @@ -1272,12 +1290,3 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/46/78/10ad9781128ed2f99dbc474f43283b13fea8ba58723e98844367531c18e9/wrapt-1.17.3-cp314-cp314t-win_arm64.whl", hash = "sha256:f38e60678850c42461d4202739f9bf1e3a737c7ad283638251e79cc49effb6b6", size = 38471, upload-time = "2025-08-12T05:52:57.784Z" }, { url = "https://files.pythonhosted.org/packages/1f/f6/a933bd70f98e9cf3e08167fc5cd7aaaca49147e48411c0bd5ae701bb2194/wrapt-1.17.3-py3-none-any.whl", hash = "sha256:7171ae35d2c33d326ac19dd8facb1e82e5fd04ef8c6c0e394d7af55a55051c22", size = 23591, upload-time = "2025-08-12T05:53:20.674Z" }, ] - -[[package]] -name = "zipp" -version = "3.23.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166", size = 25547, upload-time = "2025-06-08T17:06:39.4Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276, upload-time = "2025-06-08T17:06:38.034Z" }, -] diff --git a/examples/example-ai-together-ai/uv.lock b/examples/example-ai-together-ai/uv.lock index eb2c37bd..ab3afb7e 100644 --- a/examples/example-ai-together-ai/uv.lock +++ b/examples/example-ai-together-ai/uv.lock @@ -262,18 +262,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/57/b0/0e52c878c53f245edd3a11020f20979b3f490f245af532c7cae3027754b5/idna-3.19-py3-none-any.whl", hash = "sha256:815e7be7a7806d54abb586dc943addc79e8b2ee16915059658cbeff4b1b43bf4", size = 68550, upload-time = "2026-08-18T05:14:22.343Z" }, ] -[[package]] -name = "importlib-metadata" -version = "8.7.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "zipp" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/f3/49/3b30cad09e7771a4982d9975a8cbf64f00d4a1ececb53297f1d9a7be1b10/importlib_metadata-8.7.1.tar.gz", hash = "sha256:49fef1ae6440c182052f407c8d34a68f72efc36db9ca90dc0113398f2fdde8bb", size = 57107, upload-time = "2025-12-21T10:00:19.278Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fa/5e/f8e9a1d23b9c20a551a8a02ea3637b4642e22c2626e3a13a9a29cdea99eb/importlib_metadata-8.7.1-py3-none-any.whl", hash = "sha256:5a1f80bf1daa489495071efbb095d75a634cf28a8bc299581244063b53176151", size = 27865, upload-time = "2025-12-21T10:00:18.329Z" }, -] - [[package]] name = "jiter" version = "0.13.0" @@ -392,50 +380,80 @@ wheels = [ [[package]] name = "opentelemetry-api" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "importlib-metadata" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2c/1d/4049a9e8698361cc1a1aa03a6c59e4fa4c71e0c0f94a30f988a6876a2ae6/opentelemetry_api-1.40.0.tar.gz", hash = "sha256:159be641c0b04d11e9ecd576906462773eb97ae1b657730f0ecf64d32071569f", size = 70851, upload-time = "2026-03-04T14:17:21.555Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2e/02/6e0ae9cc61bd3169d401077b507b3ebc344745171e1051ab430be012dcd9/opentelemetry_api-1.45.1.tar.gz", hash = "sha256:aa38ed19bcc084ba42782a73255b3582283eced7ad6dddbd6695189e69adfb75", size = 72804, upload-time = "2026-10-06T17:32:58.133Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/41/f7dcf80b81ee8e71c1a2b59f14208bc723edbd89ed027a73b175abf6348e/opentelemetry_api-1.45.1-py3-none-any.whl", hash = "sha256:b31553efa588ae44bc306f863c785c5333a9ecc091248c6ee68b4b6c87fdedfb", size = 60256, upload-time = "2026-10-06T17:32:33.506Z" }, +] + +[[package]] +name = "opentelemetry-exporter-http-transport" +version = "0.66b1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/62/0c/e3ebdb4b507f66afcc905e6885a4946969bd75b45988492643356fbbdc63/opentelemetry_exporter_http_transport-0.66b1.tar.gz", hash = "sha256:443080203bf52586ce0b2ad901e8951c61833eab1aa539ae6f1f16fe9e8e7952", size = 11693, upload-time = "2026-10-06T17:32:59.65Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/69/6af86ff66492b481c6a4c05dcfd68beb47ed8ba046440a26a2aac76b95c7/opentelemetry_exporter_http_transport-0.66b1-py3-none-any.whl", hash = "sha256:2f95404bdee7f9d2d529c7de56c7bd86d014d774d8fbf137810e0167f8a492bf", size = 12155, upload-time = "2026-10-06T17:32:35.454Z" }, +] + +[package.optional-dependencies] +requests = [ + { name = "requests" }, +] + +[[package]] +name = "opentelemetry-exporter-otlp-common" +version = "0.66b1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-sdk" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cb/19/41de712173f43057e4532d42ece7d0c6d4210d353e5752433cb14987643f/opentelemetry_exporter_otlp_common-0.66b1.tar.gz", hash = "sha256:6b1403487a2185ac1feb45fd5546fdf8630ce71c36bcefaadf51e2130e9e23f9", size = 14325, upload-time = "2026-10-06T17:33:01.725Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5f/bf/93795954016c522008da367da292adceed71cca6ee1717e1d64c83089099/opentelemetry_api-1.40.0-py3-none-any.whl", hash = "sha256:82dd69331ae74b06f6a874704be0cfaa49a1650e1537d4a813b86ecef7d0ecf9", size = 68676, upload-time = "2026-03-04T14:17:01.24Z" }, + { url = "https://files.pythonhosted.org/packages/fc/39/8c23d67665c762aa51840fa06f86e902e8f6f1693bc8d7e3d98cd6e2f753/opentelemetry_exporter_otlp_common-0.66b1-py3-none-any.whl", hash = "sha256:00ff8592c3a7cb729ff3fdc7ffa12372c243bdf2163e80c180994d0c7bd83ee9", size = 12385, upload-time = "2026-10-06T17:32:38.177Z" }, ] [[package]] name = "opentelemetry-exporter-otlp-proto-common" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-proto" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/51/bc/1559d46557fe6eca0b46c88d4c2676285f1f3be2e8d06bb5d15fbffc814a/opentelemetry_exporter_otlp_proto_common-1.40.0.tar.gz", hash = "sha256:1cbee86a4064790b362a86601ee7934f368b81cd4cc2f2e163902a6e7818a0fa", size = 20416, upload-time = "2026-03-04T14:17:23.801Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c1/8e/65e85e5137991a3c493b11682151d198638a5bc1dd4b4c5f67e013c57d7c/opentelemetry_exporter_otlp_proto_common-1.45.1.tar.gz", hash = "sha256:2e4adcc3a67bcf57804fc49514f0ef64974ca7590aa3491da389852b4a0628f6", size = 18873, upload-time = "2026-10-06T17:33:04.471Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8b/ca/8f122055c97a932311a3f640273f084e738008933503d0c2563cd5d591fc/opentelemetry_exporter_otlp_proto_common-1.40.0-py3-none-any.whl", hash = "sha256:7081ff453835a82417bf38dccf122c827c3cbc94f2079b03bba02a3165f25149", size = 18369, upload-time = "2026-03-04T14:17:04.796Z" }, + { url = "https://files.pythonhosted.org/packages/84/aa/92f225d353904e7f70b8b3e3c1b02db0cf56f744c2e83c581dc372e78873/opentelemetry_exporter_otlp_proto_common-1.45.1-py3-none-any.whl", hash = "sha256:2f446183ae7047b036226f1d846c41a834b0e8755ad13b51a51dd38952eb466c", size = 15393, upload-time = "2026-10-06T17:32:41.911Z" }, ] [[package]] name = "opentelemetry-exporter-otlp-proto-http" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "googleapis-common-protos" }, { name = "opentelemetry-api" }, + { name = "opentelemetry-exporter-http-transport", extra = ["requests"] }, + { name = "opentelemetry-exporter-otlp-common" }, { name = "opentelemetry-exporter-otlp-proto-common" }, { name = "opentelemetry-proto" }, { name = "opentelemetry-sdk" }, { name = "requests" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2e/fa/73d50e2c15c56be4d000c98e24221d494674b0cc95524e2a8cb3856d95a4/opentelemetry_exporter_otlp_proto_http-1.40.0.tar.gz", hash = "sha256:db48f5e0f33217588bbc00274a31517ba830da576e59503507c839b38fa0869c", size = 17772, upload-time = "2026-03-04T14:17:25.324Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/17/26487707ea4caa97b17e6e4b5fa72133a53512ffa2f5cf7a49ef284b29cb/opentelemetry_exporter_otlp_proto_http-1.45.1.tar.gz", hash = "sha256:45c218405ce3fd879596924b1874bf9a8f6880206d61065c5a912c8e5c297fb7", size = 28839, upload-time = "2026-10-06T17:33:05.713Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/3a/8865d6754e61c9fb170cdd530a124a53769ee5f740236064816eb0ca7301/opentelemetry_exporter_otlp_proto_http-1.40.0-py3-none-any.whl", hash = "sha256:a8d1dab28f504c5d96577d6509f80a8150e44e8f45f82cdbe0e34c99ab040069", size = 19960, upload-time = "2026-03-04T14:17:07.153Z" }, + { url = "https://files.pythonhosted.org/packages/aa/1f/517eaa0187ba106a9da97160ce2add3a371812681dc440930b267f714e42/opentelemetry_exporter_otlp_proto_http-1.45.1-py3-none-any.whl", hash = "sha256:24a97cf3753c7fb52fad44a696e452ff371686339e2acf3309e2eda3d0230700", size = 22180, upload-time = "2026-10-06T17:32:43.946Z" }, ] [[package]] name = "opentelemetry-instrumentation" -version = "0.61b0" +version = "0.66b1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, @@ -443,9 +461,9 @@ dependencies = [ { name = "packaging" }, { name = "wrapt" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/da/37/6bf8e66bfcee5d3c6515b79cb2ee9ad05fe573c20f7ceb288d0e7eeec28c/opentelemetry_instrumentation-0.61b0.tar.gz", hash = "sha256:cb21b48db738c9de196eba6b805b4ff9de3b7f187e4bbf9a466fa170514f1fc7", size = 32606, upload-time = "2026-03-04T14:20:16.825Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a5/03/89e47ff8d52a4f83b343e6eb9ef1698ff45357216e5b6b2b21e0da5c5c7d/opentelemetry_instrumentation-0.66b1.tar.gz", hash = "sha256:e79a510f7d87c72d95e964ddb42193a0d9a75668c027d980eab032ea1322a5ce", size = 43308, upload-time = "2026-10-06T17:36:10.703Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d8/3e/f6f10f178b6316de67f0dfdbbb699a24fbe8917cf1743c1595fb9dcdd461/opentelemetry_instrumentation-0.61b0-py3-none-any.whl", hash = "sha256:92a93a280e69788e8f88391247cc530fd81f16f2b011979d4d6398f805cfbc63", size = 33448, upload-time = "2026-03-04T14:19:02.447Z" }, + { url = "https://files.pythonhosted.org/packages/da/b2/d1413681ff43e13ac9860df27e1226d3199ab0b97b352ceea41abcc660a5/opentelemetry_instrumentation-0.66b1-py3-none-any.whl", hash = "sha256:4c4aa14dc9a24a02325a9d4c42c4d0208dbb1374c2b1b8fe6c9392d59f3e1008", size = 36904, upload-time = "2026-10-06T17:35:11.663Z" }, ] [[package]] @@ -464,41 +482,41 @@ wheels = [ [[package]] name = "opentelemetry-proto" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "protobuf" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4c/77/dd38991db037fdfce45849491cb61de5ab000f49824a00230afb112a4392/opentelemetry_proto-1.40.0.tar.gz", hash = "sha256:03f639ca129ba513f5819810f5b1f42bcb371391405d99c168fe6937c62febcd", size = 45667, upload-time = "2026-03-04T14:17:31.194Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4b/7f/15f014fb195da6c2dbb6c71399b8e76824878718e94de6454038488eed28/opentelemetry_proto-1.45.1.tar.gz", hash = "sha256:79e0fb95e4616691a469439238aa9224d75779b3e108e895d1aa125ab29ca77c", size = 46488, upload-time = "2026-10-06T17:33:11.49Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b9/b2/189b2577dde745b15625b3214302605b1353436219d42b7912e77fa8dc24/opentelemetry_proto-1.40.0-py3-none-any.whl", hash = "sha256:266c4385d88923a23d63e353e9761af0f47a6ed0d486979777fe4de59dc9b25f", size = 72073, upload-time = "2026-03-04T14:17:16.673Z" }, + { url = "https://files.pythonhosted.org/packages/ab/9a/42ec8180a769516ae757e893b69736826efceac7332553915b4528a91c6d/opentelemetry_proto-1.45.1-py3-none-any.whl", hash = "sha256:f38e2a8413053c180cd3d2637fbb279673ec2f6a6e09c995aafa2f452c52b46e", size = 72488, upload-time = "2026-10-06T17:32:53.057Z" }, ] [[package]] name = "opentelemetry-sdk" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, { name = "opentelemetry-semantic-conventions" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/58/fd/3c3125b20ba18ce2155ba9ea74acb0ae5d25f8cd39cfd37455601b7955cc/opentelemetry_sdk-1.40.0.tar.gz", hash = "sha256:18e9f5ec20d859d268c7cb3c5198c8d105d073714db3de50b593b8c1345a48f2", size = 184252, upload-time = "2026-03-04T14:17:31.87Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/79/7392e21a1c8f0c61d90b223e31c7e48cb9d452e91a6b820ad24cca5f23c4/opentelemetry_sdk-1.45.1.tar.gz", hash = "sha256:63d24a6ca645019a631e6a51999c73e93adcac1196ca640b8ae78a7cc4762bf3", size = 218324, upload-time = "2026-10-06T17:33:13.26Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/c5/6a852903d8bfac758c6dc6e9a68b015d3c33f2f1be5e9591e0f4b69c7e0a/opentelemetry_sdk-1.40.0-py3-none-any.whl", hash = "sha256:787d2154a71f4b3d81f20524a8ce061b7db667d24e46753f32a7bc48f1c1f3f1", size = 141951, upload-time = "2026-03-04T14:17:17.961Z" }, + { url = "https://files.pythonhosted.org/packages/95/3c/87c42b4bd6dd297536f04cd9383d212ac557ecd49f2cbdcd46da1c9ef5c8/opentelemetry_sdk-1.45.1-py3-none-any.whl", hash = "sha256:c604c11dc429810812348989115fa44bd558772a3d7442afc43d024f2c250ca4", size = 140063, upload-time = "2026-10-06T17:32:55.04Z" }, ] [[package]] name = "opentelemetry-semantic-conventions" -version = "0.61b0" +version = "0.66b1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6d/c0/4ae7973f3c2cfd2b6e321f1675626f0dab0a97027cc7a297474c9c8f3d04/opentelemetry_semantic_conventions-0.61b0.tar.gz", hash = "sha256:072f65473c5d7c6dc0355b27d6c9d1a679d63b6d4b4b16a9773062cb7e31192a", size = 145755, upload-time = "2026-03-04T14:17:32.664Z" } +sdist = { url = "https://files.pythonhosted.org/packages/46/e4/dbbfb2a010c4db2224a5114638acede6fe563d33cc20fb1752cebcbe6298/opentelemetry_semantic_conventions-0.66b1.tar.gz", hash = "sha256:497ca63bf383723411e8eaf60c8779e9877633c936bb641080adab59d0eb6ec8", size = 150250, upload-time = "2026-10-06T17:33:14.073Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b2/37/cc6a55e448deaa9b27377d087da8615a3416d8ad523d5960b78dbeadd02a/opentelemetry_semantic_conventions-0.61b0-py3-none-any.whl", hash = "sha256:fa530a96be229795f8cef353739b618148b0fe2b4b3f005e60e262926c4d38e2", size = 231621, upload-time = "2026-03-04T14:17:19.33Z" }, + { url = "https://files.pythonhosted.org/packages/bc/14/67f8aa798857f8cf686f515bf93d9bb877ce952ddc8efae0fa25b45ce0d6/opentelemetry_semantic_conventions-0.66b1-py3-none-any.whl", hash = "sha256:d4cddeb4315490b35213f55e2bdc9ac54bb1e4d318927475bed62b35545e581b", size = 206279, upload-time = "2026-10-06T17:32:56.103Z" }, ] [[package]] @@ -512,7 +530,7 @@ wheels = [ [[package]] name = "posthog" -version = "7.75.4" +version = "7.75.11" source = { directory = "../../" } dependencies = [ { name = "backoff" }, @@ -877,12 +895,3 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/46/78/10ad9781128ed2f99dbc474f43283b13fea8ba58723e98844367531c18e9/wrapt-1.17.3-cp314-cp314t-win_arm64.whl", hash = "sha256:f38e60678850c42461d4202739f9bf1e3a737c7ad283638251e79cc49effb6b6", size = 38471, upload-time = "2025-08-12T05:52:57.784Z" }, { url = "https://files.pythonhosted.org/packages/1f/f6/a933bd70f98e9cf3e08167fc5cd7aaaca49147e48411c0bd5ae701bb2194/wrapt-1.17.3-py3-none-any.whl", hash = "sha256:7171ae35d2c33d326ac19dd8facb1e82e5fd04ef8c6c0e394d7af55a55051c22", size = 23591, upload-time = "2025-08-12T05:53:20.674Z" }, ] - -[[package]] -name = "zipp" -version = "3.23.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166", size = 25547, upload-time = "2025-06-08T17:06:39.4Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276, upload-time = "2025-06-08T17:06:38.034Z" }, -] diff --git a/examples/example-ai-vercel-ai-gateway/uv.lock b/examples/example-ai-vercel-ai-gateway/uv.lock index ef326447..554ae815 100644 --- a/examples/example-ai-vercel-ai-gateway/uv.lock +++ b/examples/example-ai-vercel-ai-gateway/uv.lock @@ -262,18 +262,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/57/b0/0e52c878c53f245edd3a11020f20979b3f490f245af532c7cae3027754b5/idna-3.19-py3-none-any.whl", hash = "sha256:815e7be7a7806d54abb586dc943addc79e8b2ee16915059658cbeff4b1b43bf4", size = 68550, upload-time = "2026-08-18T05:14:22.343Z" }, ] -[[package]] -name = "importlib-metadata" -version = "8.7.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "zipp" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/f3/49/3b30cad09e7771a4982d9975a8cbf64f00d4a1ececb53297f1d9a7be1b10/importlib_metadata-8.7.1.tar.gz", hash = "sha256:49fef1ae6440c182052f407c8d34a68f72efc36db9ca90dc0113398f2fdde8bb", size = 57107, upload-time = "2025-12-21T10:00:19.278Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fa/5e/f8e9a1d23b9c20a551a8a02ea3637b4642e22c2626e3a13a9a29cdea99eb/importlib_metadata-8.7.1-py3-none-any.whl", hash = "sha256:5a1f80bf1daa489495071efbb095d75a634cf28a8bc299581244063b53176151", size = 27865, upload-time = "2025-12-21T10:00:18.329Z" }, -] - [[package]] name = "jiter" version = "0.13.0" @@ -392,50 +380,80 @@ wheels = [ [[package]] name = "opentelemetry-api" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "importlib-metadata" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2c/1d/4049a9e8698361cc1a1aa03a6c59e4fa4c71e0c0f94a30f988a6876a2ae6/opentelemetry_api-1.40.0.tar.gz", hash = "sha256:159be641c0b04d11e9ecd576906462773eb97ae1b657730f0ecf64d32071569f", size = 70851, upload-time = "2026-03-04T14:17:21.555Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2e/02/6e0ae9cc61bd3169d401077b507b3ebc344745171e1051ab430be012dcd9/opentelemetry_api-1.45.1.tar.gz", hash = "sha256:aa38ed19bcc084ba42782a73255b3582283eced7ad6dddbd6695189e69adfb75", size = 72804, upload-time = "2026-10-06T17:32:58.133Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/41/f7dcf80b81ee8e71c1a2b59f14208bc723edbd89ed027a73b175abf6348e/opentelemetry_api-1.45.1-py3-none-any.whl", hash = "sha256:b31553efa588ae44bc306f863c785c5333a9ecc091248c6ee68b4b6c87fdedfb", size = 60256, upload-time = "2026-10-06T17:32:33.506Z" }, +] + +[[package]] +name = "opentelemetry-exporter-http-transport" +version = "0.66b1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/62/0c/e3ebdb4b507f66afcc905e6885a4946969bd75b45988492643356fbbdc63/opentelemetry_exporter_http_transport-0.66b1.tar.gz", hash = "sha256:443080203bf52586ce0b2ad901e8951c61833eab1aa539ae6f1f16fe9e8e7952", size = 11693, upload-time = "2026-10-06T17:32:59.65Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/69/6af86ff66492b481c6a4c05dcfd68beb47ed8ba046440a26a2aac76b95c7/opentelemetry_exporter_http_transport-0.66b1-py3-none-any.whl", hash = "sha256:2f95404bdee7f9d2d529c7de56c7bd86d014d774d8fbf137810e0167f8a492bf", size = 12155, upload-time = "2026-10-06T17:32:35.454Z" }, +] + +[package.optional-dependencies] +requests = [ + { name = "requests" }, +] + +[[package]] +name = "opentelemetry-exporter-otlp-common" +version = "0.66b1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-sdk" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cb/19/41de712173f43057e4532d42ece7d0c6d4210d353e5752433cb14987643f/opentelemetry_exporter_otlp_common-0.66b1.tar.gz", hash = "sha256:6b1403487a2185ac1feb45fd5546fdf8630ce71c36bcefaadf51e2130e9e23f9", size = 14325, upload-time = "2026-10-06T17:33:01.725Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5f/bf/93795954016c522008da367da292adceed71cca6ee1717e1d64c83089099/opentelemetry_api-1.40.0-py3-none-any.whl", hash = "sha256:82dd69331ae74b06f6a874704be0cfaa49a1650e1537d4a813b86ecef7d0ecf9", size = 68676, upload-time = "2026-03-04T14:17:01.24Z" }, + { url = "https://files.pythonhosted.org/packages/fc/39/8c23d67665c762aa51840fa06f86e902e8f6f1693bc8d7e3d98cd6e2f753/opentelemetry_exporter_otlp_common-0.66b1-py3-none-any.whl", hash = "sha256:00ff8592c3a7cb729ff3fdc7ffa12372c243bdf2163e80c180994d0c7bd83ee9", size = 12385, upload-time = "2026-10-06T17:32:38.177Z" }, ] [[package]] name = "opentelemetry-exporter-otlp-proto-common" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-proto" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/51/bc/1559d46557fe6eca0b46c88d4c2676285f1f3be2e8d06bb5d15fbffc814a/opentelemetry_exporter_otlp_proto_common-1.40.0.tar.gz", hash = "sha256:1cbee86a4064790b362a86601ee7934f368b81cd4cc2f2e163902a6e7818a0fa", size = 20416, upload-time = "2026-03-04T14:17:23.801Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c1/8e/65e85e5137991a3c493b11682151d198638a5bc1dd4b4c5f67e013c57d7c/opentelemetry_exporter_otlp_proto_common-1.45.1.tar.gz", hash = "sha256:2e4adcc3a67bcf57804fc49514f0ef64974ca7590aa3491da389852b4a0628f6", size = 18873, upload-time = "2026-10-06T17:33:04.471Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8b/ca/8f122055c97a932311a3f640273f084e738008933503d0c2563cd5d591fc/opentelemetry_exporter_otlp_proto_common-1.40.0-py3-none-any.whl", hash = "sha256:7081ff453835a82417bf38dccf122c827c3cbc94f2079b03bba02a3165f25149", size = 18369, upload-time = "2026-03-04T14:17:04.796Z" }, + { url = "https://files.pythonhosted.org/packages/84/aa/92f225d353904e7f70b8b3e3c1b02db0cf56f744c2e83c581dc372e78873/opentelemetry_exporter_otlp_proto_common-1.45.1-py3-none-any.whl", hash = "sha256:2f446183ae7047b036226f1d846c41a834b0e8755ad13b51a51dd38952eb466c", size = 15393, upload-time = "2026-10-06T17:32:41.911Z" }, ] [[package]] name = "opentelemetry-exporter-otlp-proto-http" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "googleapis-common-protos" }, { name = "opentelemetry-api" }, + { name = "opentelemetry-exporter-http-transport", extra = ["requests"] }, + { name = "opentelemetry-exporter-otlp-common" }, { name = "opentelemetry-exporter-otlp-proto-common" }, { name = "opentelemetry-proto" }, { name = "opentelemetry-sdk" }, { name = "requests" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2e/fa/73d50e2c15c56be4d000c98e24221d494674b0cc95524e2a8cb3856d95a4/opentelemetry_exporter_otlp_proto_http-1.40.0.tar.gz", hash = "sha256:db48f5e0f33217588bbc00274a31517ba830da576e59503507c839b38fa0869c", size = 17772, upload-time = "2026-03-04T14:17:25.324Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/17/26487707ea4caa97b17e6e4b5fa72133a53512ffa2f5cf7a49ef284b29cb/opentelemetry_exporter_otlp_proto_http-1.45.1.tar.gz", hash = "sha256:45c218405ce3fd879596924b1874bf9a8f6880206d61065c5a912c8e5c297fb7", size = 28839, upload-time = "2026-10-06T17:33:05.713Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/3a/8865d6754e61c9fb170cdd530a124a53769ee5f740236064816eb0ca7301/opentelemetry_exporter_otlp_proto_http-1.40.0-py3-none-any.whl", hash = "sha256:a8d1dab28f504c5d96577d6509f80a8150e44e8f45f82cdbe0e34c99ab040069", size = 19960, upload-time = "2026-03-04T14:17:07.153Z" }, + { url = "https://files.pythonhosted.org/packages/aa/1f/517eaa0187ba106a9da97160ce2add3a371812681dc440930b267f714e42/opentelemetry_exporter_otlp_proto_http-1.45.1-py3-none-any.whl", hash = "sha256:24a97cf3753c7fb52fad44a696e452ff371686339e2acf3309e2eda3d0230700", size = 22180, upload-time = "2026-10-06T17:32:43.946Z" }, ] [[package]] name = "opentelemetry-instrumentation" -version = "0.61b0" +version = "0.66b1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, @@ -443,9 +461,9 @@ dependencies = [ { name = "packaging" }, { name = "wrapt" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/da/37/6bf8e66bfcee5d3c6515b79cb2ee9ad05fe573c20f7ceb288d0e7eeec28c/opentelemetry_instrumentation-0.61b0.tar.gz", hash = "sha256:cb21b48db738c9de196eba6b805b4ff9de3b7f187e4bbf9a466fa170514f1fc7", size = 32606, upload-time = "2026-03-04T14:20:16.825Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a5/03/89e47ff8d52a4f83b343e6eb9ef1698ff45357216e5b6b2b21e0da5c5c7d/opentelemetry_instrumentation-0.66b1.tar.gz", hash = "sha256:e79a510f7d87c72d95e964ddb42193a0d9a75668c027d980eab032ea1322a5ce", size = 43308, upload-time = "2026-10-06T17:36:10.703Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d8/3e/f6f10f178b6316de67f0dfdbbb699a24fbe8917cf1743c1595fb9dcdd461/opentelemetry_instrumentation-0.61b0-py3-none-any.whl", hash = "sha256:92a93a280e69788e8f88391247cc530fd81f16f2b011979d4d6398f805cfbc63", size = 33448, upload-time = "2026-03-04T14:19:02.447Z" }, + { url = "https://files.pythonhosted.org/packages/da/b2/d1413681ff43e13ac9860df27e1226d3199ab0b97b352ceea41abcc660a5/opentelemetry_instrumentation-0.66b1-py3-none-any.whl", hash = "sha256:4c4aa14dc9a24a02325a9d4c42c4d0208dbb1374c2b1b8fe6c9392d59f3e1008", size = 36904, upload-time = "2026-10-06T17:35:11.663Z" }, ] [[package]] @@ -464,41 +482,41 @@ wheels = [ [[package]] name = "opentelemetry-proto" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "protobuf" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4c/77/dd38991db037fdfce45849491cb61de5ab000f49824a00230afb112a4392/opentelemetry_proto-1.40.0.tar.gz", hash = "sha256:03f639ca129ba513f5819810f5b1f42bcb371391405d99c168fe6937c62febcd", size = 45667, upload-time = "2026-03-04T14:17:31.194Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4b/7f/15f014fb195da6c2dbb6c71399b8e76824878718e94de6454038488eed28/opentelemetry_proto-1.45.1.tar.gz", hash = "sha256:79e0fb95e4616691a469439238aa9224d75779b3e108e895d1aa125ab29ca77c", size = 46488, upload-time = "2026-10-06T17:33:11.49Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b9/b2/189b2577dde745b15625b3214302605b1353436219d42b7912e77fa8dc24/opentelemetry_proto-1.40.0-py3-none-any.whl", hash = "sha256:266c4385d88923a23d63e353e9761af0f47a6ed0d486979777fe4de59dc9b25f", size = 72073, upload-time = "2026-03-04T14:17:16.673Z" }, + { url = "https://files.pythonhosted.org/packages/ab/9a/42ec8180a769516ae757e893b69736826efceac7332553915b4528a91c6d/opentelemetry_proto-1.45.1-py3-none-any.whl", hash = "sha256:f38e2a8413053c180cd3d2637fbb279673ec2f6a6e09c995aafa2f452c52b46e", size = 72488, upload-time = "2026-10-06T17:32:53.057Z" }, ] [[package]] name = "opentelemetry-sdk" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, { name = "opentelemetry-semantic-conventions" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/58/fd/3c3125b20ba18ce2155ba9ea74acb0ae5d25f8cd39cfd37455601b7955cc/opentelemetry_sdk-1.40.0.tar.gz", hash = "sha256:18e9f5ec20d859d268c7cb3c5198c8d105d073714db3de50b593b8c1345a48f2", size = 184252, upload-time = "2026-03-04T14:17:31.87Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/79/7392e21a1c8f0c61d90b223e31c7e48cb9d452e91a6b820ad24cca5f23c4/opentelemetry_sdk-1.45.1.tar.gz", hash = "sha256:63d24a6ca645019a631e6a51999c73e93adcac1196ca640b8ae78a7cc4762bf3", size = 218324, upload-time = "2026-10-06T17:33:13.26Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/c5/6a852903d8bfac758c6dc6e9a68b015d3c33f2f1be5e9591e0f4b69c7e0a/opentelemetry_sdk-1.40.0-py3-none-any.whl", hash = "sha256:787d2154a71f4b3d81f20524a8ce061b7db667d24e46753f32a7bc48f1c1f3f1", size = 141951, upload-time = "2026-03-04T14:17:17.961Z" }, + { url = "https://files.pythonhosted.org/packages/95/3c/87c42b4bd6dd297536f04cd9383d212ac557ecd49f2cbdcd46da1c9ef5c8/opentelemetry_sdk-1.45.1-py3-none-any.whl", hash = "sha256:c604c11dc429810812348989115fa44bd558772a3d7442afc43d024f2c250ca4", size = 140063, upload-time = "2026-10-06T17:32:55.04Z" }, ] [[package]] name = "opentelemetry-semantic-conventions" -version = "0.61b0" +version = "0.66b1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6d/c0/4ae7973f3c2cfd2b6e321f1675626f0dab0a97027cc7a297474c9c8f3d04/opentelemetry_semantic_conventions-0.61b0.tar.gz", hash = "sha256:072f65473c5d7c6dc0355b27d6c9d1a679d63b6d4b4b16a9773062cb7e31192a", size = 145755, upload-time = "2026-03-04T14:17:32.664Z" } +sdist = { url = "https://files.pythonhosted.org/packages/46/e4/dbbfb2a010c4db2224a5114638acede6fe563d33cc20fb1752cebcbe6298/opentelemetry_semantic_conventions-0.66b1.tar.gz", hash = "sha256:497ca63bf383723411e8eaf60c8779e9877633c936bb641080adab59d0eb6ec8", size = 150250, upload-time = "2026-10-06T17:33:14.073Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b2/37/cc6a55e448deaa9b27377d087da8615a3416d8ad523d5960b78dbeadd02a/opentelemetry_semantic_conventions-0.61b0-py3-none-any.whl", hash = "sha256:fa530a96be229795f8cef353739b618148b0fe2b4b3f005e60e262926c4d38e2", size = 231621, upload-time = "2026-03-04T14:17:19.33Z" }, + { url = "https://files.pythonhosted.org/packages/bc/14/67f8aa798857f8cf686f515bf93d9bb877ce952ddc8efae0fa25b45ce0d6/opentelemetry_semantic_conventions-0.66b1-py3-none-any.whl", hash = "sha256:d4cddeb4315490b35213f55e2bdc9ac54bb1e4d318927475bed62b35545e581b", size = 206279, upload-time = "2026-10-06T17:32:56.103Z" }, ] [[package]] @@ -512,7 +530,7 @@ wheels = [ [[package]] name = "posthog" -version = "7.75.4" +version = "7.75.11" source = { directory = "../../" } dependencies = [ { name = "backoff" }, @@ -877,12 +895,3 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/46/78/10ad9781128ed2f99dbc474f43283b13fea8ba58723e98844367531c18e9/wrapt-1.17.3-cp314-cp314t-win_arm64.whl", hash = "sha256:f38e60678850c42461d4202739f9bf1e3a737c7ad283638251e79cc49effb6b6", size = 38471, upload-time = "2025-08-12T05:52:57.784Z" }, { url = "https://files.pythonhosted.org/packages/1f/f6/a933bd70f98e9cf3e08167fc5cd7aaaca49147e48411c0bd5ae701bb2194/wrapt-1.17.3-py3-none-any.whl", hash = "sha256:7171ae35d2c33d326ac19dd8facb1e82e5fd04ef8c6c0e394d7af55a55051c22", size = 23591, upload-time = "2025-08-12T05:53:20.674Z" }, ] - -[[package]] -name = "zipp" -version = "3.23.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166", size = 25547, upload-time = "2025-06-08T17:06:39.4Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276, upload-time = "2025-06-08T17:06:38.034Z" }, -] diff --git a/examples/example-ai-xai/uv.lock b/examples/example-ai-xai/uv.lock index 24f779a2..25459e60 100644 --- a/examples/example-ai-xai/uv.lock +++ b/examples/example-ai-xai/uv.lock @@ -262,18 +262,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/57/b0/0e52c878c53f245edd3a11020f20979b3f490f245af532c7cae3027754b5/idna-3.19-py3-none-any.whl", hash = "sha256:815e7be7a7806d54abb586dc943addc79e8b2ee16915059658cbeff4b1b43bf4", size = 68550, upload-time = "2026-08-18T05:14:22.343Z" }, ] -[[package]] -name = "importlib-metadata" -version = "8.7.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "zipp" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/f3/49/3b30cad09e7771a4982d9975a8cbf64f00d4a1ececb53297f1d9a7be1b10/importlib_metadata-8.7.1.tar.gz", hash = "sha256:49fef1ae6440c182052f407c8d34a68f72efc36db9ca90dc0113398f2fdde8bb", size = 57107, upload-time = "2025-12-21T10:00:19.278Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fa/5e/f8e9a1d23b9c20a551a8a02ea3637b4642e22c2626e3a13a9a29cdea99eb/importlib_metadata-8.7.1-py3-none-any.whl", hash = "sha256:5a1f80bf1daa489495071efbb095d75a634cf28a8bc299581244063b53176151", size = 27865, upload-time = "2025-12-21T10:00:18.329Z" }, -] - [[package]] name = "jiter" version = "0.13.0" @@ -392,50 +380,80 @@ wheels = [ [[package]] name = "opentelemetry-api" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "importlib-metadata" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2c/1d/4049a9e8698361cc1a1aa03a6c59e4fa4c71e0c0f94a30f988a6876a2ae6/opentelemetry_api-1.40.0.tar.gz", hash = "sha256:159be641c0b04d11e9ecd576906462773eb97ae1b657730f0ecf64d32071569f", size = 70851, upload-time = "2026-03-04T14:17:21.555Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2e/02/6e0ae9cc61bd3169d401077b507b3ebc344745171e1051ab430be012dcd9/opentelemetry_api-1.45.1.tar.gz", hash = "sha256:aa38ed19bcc084ba42782a73255b3582283eced7ad6dddbd6695189e69adfb75", size = 72804, upload-time = "2026-10-06T17:32:58.133Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/41/f7dcf80b81ee8e71c1a2b59f14208bc723edbd89ed027a73b175abf6348e/opentelemetry_api-1.45.1-py3-none-any.whl", hash = "sha256:b31553efa588ae44bc306f863c785c5333a9ecc091248c6ee68b4b6c87fdedfb", size = 60256, upload-time = "2026-10-06T17:32:33.506Z" }, +] + +[[package]] +name = "opentelemetry-exporter-http-transport" +version = "0.66b1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/62/0c/e3ebdb4b507f66afcc905e6885a4946969bd75b45988492643356fbbdc63/opentelemetry_exporter_http_transport-0.66b1.tar.gz", hash = "sha256:443080203bf52586ce0b2ad901e8951c61833eab1aa539ae6f1f16fe9e8e7952", size = 11693, upload-time = "2026-10-06T17:32:59.65Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/69/6af86ff66492b481c6a4c05dcfd68beb47ed8ba046440a26a2aac76b95c7/opentelemetry_exporter_http_transport-0.66b1-py3-none-any.whl", hash = "sha256:2f95404bdee7f9d2d529c7de56c7bd86d014d774d8fbf137810e0167f8a492bf", size = 12155, upload-time = "2026-10-06T17:32:35.454Z" }, +] + +[package.optional-dependencies] +requests = [ + { name = "requests" }, +] + +[[package]] +name = "opentelemetry-exporter-otlp-common" +version = "0.66b1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-sdk" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cb/19/41de712173f43057e4532d42ece7d0c6d4210d353e5752433cb14987643f/opentelemetry_exporter_otlp_common-0.66b1.tar.gz", hash = "sha256:6b1403487a2185ac1feb45fd5546fdf8630ce71c36bcefaadf51e2130e9e23f9", size = 14325, upload-time = "2026-10-06T17:33:01.725Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5f/bf/93795954016c522008da367da292adceed71cca6ee1717e1d64c83089099/opentelemetry_api-1.40.0-py3-none-any.whl", hash = "sha256:82dd69331ae74b06f6a874704be0cfaa49a1650e1537d4a813b86ecef7d0ecf9", size = 68676, upload-time = "2026-03-04T14:17:01.24Z" }, + { url = "https://files.pythonhosted.org/packages/fc/39/8c23d67665c762aa51840fa06f86e902e8f6f1693bc8d7e3d98cd6e2f753/opentelemetry_exporter_otlp_common-0.66b1-py3-none-any.whl", hash = "sha256:00ff8592c3a7cb729ff3fdc7ffa12372c243bdf2163e80c180994d0c7bd83ee9", size = 12385, upload-time = "2026-10-06T17:32:38.177Z" }, ] [[package]] name = "opentelemetry-exporter-otlp-proto-common" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-proto" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/51/bc/1559d46557fe6eca0b46c88d4c2676285f1f3be2e8d06bb5d15fbffc814a/opentelemetry_exporter_otlp_proto_common-1.40.0.tar.gz", hash = "sha256:1cbee86a4064790b362a86601ee7934f368b81cd4cc2f2e163902a6e7818a0fa", size = 20416, upload-time = "2026-03-04T14:17:23.801Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c1/8e/65e85e5137991a3c493b11682151d198638a5bc1dd4b4c5f67e013c57d7c/opentelemetry_exporter_otlp_proto_common-1.45.1.tar.gz", hash = "sha256:2e4adcc3a67bcf57804fc49514f0ef64974ca7590aa3491da389852b4a0628f6", size = 18873, upload-time = "2026-10-06T17:33:04.471Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8b/ca/8f122055c97a932311a3f640273f084e738008933503d0c2563cd5d591fc/opentelemetry_exporter_otlp_proto_common-1.40.0-py3-none-any.whl", hash = "sha256:7081ff453835a82417bf38dccf122c827c3cbc94f2079b03bba02a3165f25149", size = 18369, upload-time = "2026-03-04T14:17:04.796Z" }, + { url = "https://files.pythonhosted.org/packages/84/aa/92f225d353904e7f70b8b3e3c1b02db0cf56f744c2e83c581dc372e78873/opentelemetry_exporter_otlp_proto_common-1.45.1-py3-none-any.whl", hash = "sha256:2f446183ae7047b036226f1d846c41a834b0e8755ad13b51a51dd38952eb466c", size = 15393, upload-time = "2026-10-06T17:32:41.911Z" }, ] [[package]] name = "opentelemetry-exporter-otlp-proto-http" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "googleapis-common-protos" }, { name = "opentelemetry-api" }, + { name = "opentelemetry-exporter-http-transport", extra = ["requests"] }, + { name = "opentelemetry-exporter-otlp-common" }, { name = "opentelemetry-exporter-otlp-proto-common" }, { name = "opentelemetry-proto" }, { name = "opentelemetry-sdk" }, { name = "requests" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2e/fa/73d50e2c15c56be4d000c98e24221d494674b0cc95524e2a8cb3856d95a4/opentelemetry_exporter_otlp_proto_http-1.40.0.tar.gz", hash = "sha256:db48f5e0f33217588bbc00274a31517ba830da576e59503507c839b38fa0869c", size = 17772, upload-time = "2026-03-04T14:17:25.324Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/17/26487707ea4caa97b17e6e4b5fa72133a53512ffa2f5cf7a49ef284b29cb/opentelemetry_exporter_otlp_proto_http-1.45.1.tar.gz", hash = "sha256:45c218405ce3fd879596924b1874bf9a8f6880206d61065c5a912c8e5c297fb7", size = 28839, upload-time = "2026-10-06T17:33:05.713Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/3a/8865d6754e61c9fb170cdd530a124a53769ee5f740236064816eb0ca7301/opentelemetry_exporter_otlp_proto_http-1.40.0-py3-none-any.whl", hash = "sha256:a8d1dab28f504c5d96577d6509f80a8150e44e8f45f82cdbe0e34c99ab040069", size = 19960, upload-time = "2026-03-04T14:17:07.153Z" }, + { url = "https://files.pythonhosted.org/packages/aa/1f/517eaa0187ba106a9da97160ce2add3a371812681dc440930b267f714e42/opentelemetry_exporter_otlp_proto_http-1.45.1-py3-none-any.whl", hash = "sha256:24a97cf3753c7fb52fad44a696e452ff371686339e2acf3309e2eda3d0230700", size = 22180, upload-time = "2026-10-06T17:32:43.946Z" }, ] [[package]] name = "opentelemetry-instrumentation" -version = "0.61b0" +version = "0.66b1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, @@ -443,9 +461,9 @@ dependencies = [ { name = "packaging" }, { name = "wrapt" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/da/37/6bf8e66bfcee5d3c6515b79cb2ee9ad05fe573c20f7ceb288d0e7eeec28c/opentelemetry_instrumentation-0.61b0.tar.gz", hash = "sha256:cb21b48db738c9de196eba6b805b4ff9de3b7f187e4bbf9a466fa170514f1fc7", size = 32606, upload-time = "2026-03-04T14:20:16.825Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a5/03/89e47ff8d52a4f83b343e6eb9ef1698ff45357216e5b6b2b21e0da5c5c7d/opentelemetry_instrumentation-0.66b1.tar.gz", hash = "sha256:e79a510f7d87c72d95e964ddb42193a0d9a75668c027d980eab032ea1322a5ce", size = 43308, upload-time = "2026-10-06T17:36:10.703Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d8/3e/f6f10f178b6316de67f0dfdbbb699a24fbe8917cf1743c1595fb9dcdd461/opentelemetry_instrumentation-0.61b0-py3-none-any.whl", hash = "sha256:92a93a280e69788e8f88391247cc530fd81f16f2b011979d4d6398f805cfbc63", size = 33448, upload-time = "2026-03-04T14:19:02.447Z" }, + { url = "https://files.pythonhosted.org/packages/da/b2/d1413681ff43e13ac9860df27e1226d3199ab0b97b352ceea41abcc660a5/opentelemetry_instrumentation-0.66b1-py3-none-any.whl", hash = "sha256:4c4aa14dc9a24a02325a9d4c42c4d0208dbb1374c2b1b8fe6c9392d59f3e1008", size = 36904, upload-time = "2026-10-06T17:35:11.663Z" }, ] [[package]] @@ -464,41 +482,41 @@ wheels = [ [[package]] name = "opentelemetry-proto" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "protobuf" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4c/77/dd38991db037fdfce45849491cb61de5ab000f49824a00230afb112a4392/opentelemetry_proto-1.40.0.tar.gz", hash = "sha256:03f639ca129ba513f5819810f5b1f42bcb371391405d99c168fe6937c62febcd", size = 45667, upload-time = "2026-03-04T14:17:31.194Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4b/7f/15f014fb195da6c2dbb6c71399b8e76824878718e94de6454038488eed28/opentelemetry_proto-1.45.1.tar.gz", hash = "sha256:79e0fb95e4616691a469439238aa9224d75779b3e108e895d1aa125ab29ca77c", size = 46488, upload-time = "2026-10-06T17:33:11.49Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b9/b2/189b2577dde745b15625b3214302605b1353436219d42b7912e77fa8dc24/opentelemetry_proto-1.40.0-py3-none-any.whl", hash = "sha256:266c4385d88923a23d63e353e9761af0f47a6ed0d486979777fe4de59dc9b25f", size = 72073, upload-time = "2026-03-04T14:17:16.673Z" }, + { url = "https://files.pythonhosted.org/packages/ab/9a/42ec8180a769516ae757e893b69736826efceac7332553915b4528a91c6d/opentelemetry_proto-1.45.1-py3-none-any.whl", hash = "sha256:f38e2a8413053c180cd3d2637fbb279673ec2f6a6e09c995aafa2f452c52b46e", size = 72488, upload-time = "2026-10-06T17:32:53.057Z" }, ] [[package]] name = "opentelemetry-sdk" -version = "1.40.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, { name = "opentelemetry-semantic-conventions" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/58/fd/3c3125b20ba18ce2155ba9ea74acb0ae5d25f8cd39cfd37455601b7955cc/opentelemetry_sdk-1.40.0.tar.gz", hash = "sha256:18e9f5ec20d859d268c7cb3c5198c8d105d073714db3de50b593b8c1345a48f2", size = 184252, upload-time = "2026-03-04T14:17:31.87Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/79/7392e21a1c8f0c61d90b223e31c7e48cb9d452e91a6b820ad24cca5f23c4/opentelemetry_sdk-1.45.1.tar.gz", hash = "sha256:63d24a6ca645019a631e6a51999c73e93adcac1196ca640b8ae78a7cc4762bf3", size = 218324, upload-time = "2026-10-06T17:33:13.26Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/c5/6a852903d8bfac758c6dc6e9a68b015d3c33f2f1be5e9591e0f4b69c7e0a/opentelemetry_sdk-1.40.0-py3-none-any.whl", hash = "sha256:787d2154a71f4b3d81f20524a8ce061b7db667d24e46753f32a7bc48f1c1f3f1", size = 141951, upload-time = "2026-03-04T14:17:17.961Z" }, + { url = "https://files.pythonhosted.org/packages/95/3c/87c42b4bd6dd297536f04cd9383d212ac557ecd49f2cbdcd46da1c9ef5c8/opentelemetry_sdk-1.45.1-py3-none-any.whl", hash = "sha256:c604c11dc429810812348989115fa44bd558772a3d7442afc43d024f2c250ca4", size = 140063, upload-time = "2026-10-06T17:32:55.04Z" }, ] [[package]] name = "opentelemetry-semantic-conventions" -version = "0.61b0" +version = "0.66b1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6d/c0/4ae7973f3c2cfd2b6e321f1675626f0dab0a97027cc7a297474c9c8f3d04/opentelemetry_semantic_conventions-0.61b0.tar.gz", hash = "sha256:072f65473c5d7c6dc0355b27d6c9d1a679d63b6d4b4b16a9773062cb7e31192a", size = 145755, upload-time = "2026-03-04T14:17:32.664Z" } +sdist = { url = "https://files.pythonhosted.org/packages/46/e4/dbbfb2a010c4db2224a5114638acede6fe563d33cc20fb1752cebcbe6298/opentelemetry_semantic_conventions-0.66b1.tar.gz", hash = "sha256:497ca63bf383723411e8eaf60c8779e9877633c936bb641080adab59d0eb6ec8", size = 150250, upload-time = "2026-10-06T17:33:14.073Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b2/37/cc6a55e448deaa9b27377d087da8615a3416d8ad523d5960b78dbeadd02a/opentelemetry_semantic_conventions-0.61b0-py3-none-any.whl", hash = "sha256:fa530a96be229795f8cef353739b618148b0fe2b4b3f005e60e262926c4d38e2", size = 231621, upload-time = "2026-03-04T14:17:19.33Z" }, + { url = "https://files.pythonhosted.org/packages/bc/14/67f8aa798857f8cf686f515bf93d9bb877ce952ddc8efae0fa25b45ce0d6/opentelemetry_semantic_conventions-0.66b1-py3-none-any.whl", hash = "sha256:d4cddeb4315490b35213f55e2bdc9ac54bb1e4d318927475bed62b35545e581b", size = 206279, upload-time = "2026-10-06T17:32:56.103Z" }, ] [[package]] @@ -512,7 +530,7 @@ wheels = [ [[package]] name = "posthog" -version = "7.75.4" +version = "7.75.11" source = { directory = "../../" } dependencies = [ { name = "backoff" }, @@ -877,12 +895,3 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/46/78/10ad9781128ed2f99dbc474f43283b13fea8ba58723e98844367531c18e9/wrapt-1.17.3-cp314-cp314t-win_arm64.whl", hash = "sha256:f38e60678850c42461d4202739f9bf1e3a737c7ad283638251e79cc49effb6b6", size = 38471, upload-time = "2025-08-12T05:52:57.784Z" }, { url = "https://files.pythonhosted.org/packages/1f/f6/a933bd70f98e9cf3e08167fc5cd7aaaca49147e48411c0bd5ae701bb2194/wrapt-1.17.3-py3-none-any.whl", hash = "sha256:7171ae35d2c33d326ac19dd8facb1e82e5fd04ef8c6c0e394d7af55a55051c22", size = 23591, upload-time = "2025-08-12T05:53:20.674Z" }, ] - -[[package]] -name = "zipp" -version = "3.23.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166", size = 25547, upload-time = "2025-06-08T17:06:39.4Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276, upload-time = "2025-06-08T17:06:38.034Z" }, -] From e98e17a08412c47903063c34b43b3ae5eea5e354 Mon Sep 17 00:00:00 2001 From: Radu Raicea Date: Tue, 27 Oct 2026 09:01:42 -0400 Subject: [PATCH 22/60] feat(llma): support per-item offline evaluation models (#1130) (cherry picked from commit 284340d6ba6df08f68c27eac750b13f3ec89ab19) --- .sampo/changesets/jovial-shaman-kullervo.md | 5 + .../example-offline-evaluations/README.md | 5 + posthog/ai/evaluations/_types.py | 8 + posthog/test/ai/test_evaluations.py | 180 +++++++++++++++++- posthog/test/ai/test_evaluations_datasets.py | 51 +++++ posthog/test/ai/test_evaluations_transport.py | 91 +++++++++ references/public_api_snapshot.txt | 4 +- 7 files changed, 341 insertions(+), 3 deletions(-) create mode 100644 .sampo/changesets/jovial-shaman-kullervo.md diff --git a/.sampo/changesets/jovial-shaman-kullervo.md b/.sampo/changesets/jovial-shaman-kullervo.md new file mode 100644 index 00000000..1138e5cc --- /dev/null +++ b/.sampo/changesets/jovial-shaman-kullervo.md @@ -0,0 +1,5 @@ +--- +pypi/posthog: minor +--- + +Add optional per-item model_version to offline evaluations and dataset conversion, preserving model attribution across serialization, retries, and recovery. diff --git a/examples/example-offline-evaluations/README.md b/examples/example-offline-evaluations/README.md index 7da7dd1c..00dfec78 100644 --- a/examples/example-offline-evaluations/README.md +++ b/examples/example-offline-evaluations/README.md @@ -65,6 +65,7 @@ with OfflineEvaluations( output="Paris", expected_output="Paris", case_key="capital-france", + model_version="model-b", ) receipt = experiment.upload_result( item=item, @@ -77,6 +78,8 @@ with OfflineEvaluations( Use one `EvaluationItem` for the application's execution and reuse it across scorer results. `upload_results([EvaluationResult(...), ...])` uploads a finite sequence, eagerly and sequentially. It splits at 1,000 items/results or 5 MiB of UTF-8 JSON per request, whichever comes first. Item payloads are limited to 1 MiB; result payloads to 256 KiB. Payload JSON supports at most 32 nested containers. The SDK raises for invalid values and oversized payloads without truncating them. Memory use scales with the sequence you supply, so submit caller-sized batches for large datasets. +Set `model_version` on each item to compare executions from different models in one experiment. It identifies the application's model and overrides the default supplied to `create_experiment(model_version=...)`. Omit it or pass `None` to inherit that default on the server; without either value, the model remains unknown. Explicit model identifiers must be nonempty strings of at most 255 characters and are preserved without trimming. The declared model is immutable item content: changing, adding, or removing it after acceptance causes a `content_conflict`, even if the effective model is unchanged. Create a new item for a different model execution, and preserve the original declaration for retries. + `status="ok"` means the evaluator ran successfully. Its boolean, number, or category-list `value` is uploaded with its original polarity. Numeric scores use finite double-precision (binary64) values; integer inputs are rejected if conversion would change their value. Use `error`, `skipped`, or `not_applicable` without a value for other outcomes. `error_code` and a non-null `error_message` are valid only with `status="error"`. Omitted payload fields, explicit `None`, and empty payloads are distinct: @@ -154,6 +157,8 @@ This example uses string inputs. Replace `capital_answer` and the equality check `from_dataset_item` copies input, expected output, metadata, and immutable item provenance. It never uses `source_output` as a new execution's output or expected answer, and never uses the original source trace as the new application's trace. Pass `application_trace_id` explicitly for the new execution. Omit `output` if execution failed before producing one, then upload an error outcome. Omitted output differs from `output=None`. +Pass `EvaluationItem.from_dataset_item(case, output=output, model_version="model-b")` to attribute a dataset execution to a model. Calling it again with another model creates a new execution UUID while preserving the same case key and dataset-item version. Omitting the model or passing `None` leaves experiment fallback to the server; dataset metadata and source traces never supply the execution model. + ### Snapshot and pagination behavior - `snapshot(dataset_id, revision=12)` selects a historical **number**. The returned `revision_id` is the **UUID** to pass to `create_experiment`. The backend derives the remaining hosted-dataset identifiers from that UUID. diff --git a/posthog/ai/evaluations/_types.py b/posthog/ai/evaluations/_types.py index 7992ac1c..595f0692 100644 --- a/posthog/ai/evaluations/_types.py +++ b/posthog/ai/evaluations/_types.py @@ -83,6 +83,8 @@ class EvaluationItem: Payload values are copied as JSON. Omitted fields differ from explicit None. Use ``payload={}`` for an explicitly empty payload. Save ``to_dict()`` to restore exactly the same declaration after restarting the process. + ``model_version`` identifies this execution's model and overrides the + experiment default. Omit it or pass None to inherit the default on the server. """ id: str @@ -102,6 +104,7 @@ def __init__( dataset_item_version_identifier: str | None = None, dataset_item_version_id: str | UUID | None = None, application_trace_id: str | None = None, + model_version: str | None = None, payload: dict[str, JSONValue] | _Unset = _UNSET, ) -> None: identity = _uuid(uuid4() if id is None else id) @@ -112,6 +115,7 @@ def __init__( "dataset_item_identifier": dataset_item_identifier, "dataset_item_version_identifier": dataset_item_version_identifier, "application_trace_id": application_trace_id, + "model_version": model_version, }.items(): if value is not None: data[key] = _identifier(value, key) @@ -146,6 +150,7 @@ def from_dataset_item( case_key: str | None = None, trial: str | None = None, application_trace_id: str | None = None, + model_version: str | None = None, ) -> EvaluationItem: """Declare one execution of a dataset case, preserving its exact version. @@ -156,6 +161,8 @@ def from_dataset_item( ``case_key`` defaults to the caller-owned client item ID, or stable item UUID. Omit output for an execution that failed before producing one. Use the owning snapshot's revision_id when creating the experiment. + Supply ``model_version`` for this execution's model; omitted or None + inherits the experiment default, never dataset metadata or source traces. """ if not isinstance(item, DatasetItem): raise TypeError("Expected a DatasetItem.") @@ -173,6 +180,7 @@ def from_dataset_item( dataset_item_version_identifier=item.version_id, dataset_item_version_id=item.version_id, application_trace_id=application_trace_id, + model_version=model_version, ) @classmethod diff --git a/posthog/test/ai/test_evaluations.py b/posthog/test/ai/test_evaluations.py index b2e76d0e..99bcd962 100644 --- a/posthog/test/ai/test_evaluations.py +++ b/posthog/test/ai/test_evaluations.py @@ -276,7 +276,9 @@ def test_resume_is_local_and_supports_references_without_scorer_discovery(client def test_same_item_is_declared_once_for_multiple_scorers(client): evaluations, transport = client - item = EvaluationItem(input={"question": "Paris?"}, output="France") + item = EvaluationItem( + input={"question": "Paris?"}, output="France", model_version="model-b" + ) results = [ result(item), EvaluationResult(item=item, scorer_version_id=OTHER_VERSION_ID, value=True), @@ -286,11 +288,136 @@ def test_same_item_is_declared_once_for_multiple_scorers(client): ) body = json.loads(transport.request.call_args.kwargs["body"]) assert body["items"] == [item.to_dict()] + assert body["items"][0]["model_version"] == "model-b" + assert all("model_version" not in row for row in body["results"]) assert len(receipt.chunks) == 1 assert len(receipt.chunks[0].results) == 2 assert len(receipt.chunks[0].items) == 1 +@pytest.mark.parametrize("experiment_options", [{}, {"model_version": "model-a"}, None]) +def test_item_models_are_declared_without_resolving_experiment_defaults( + client, experiment_options +): + evaluations, transport = client + if experiment_options is None: + experiment = evaluations.resume_experiment(EXPERIMENT_ID) + transport.request.assert_not_called() + else: + experiment = invoke( + evaluations.create_experiment, name="Models", **experiment_options + ) + creation = json.loads(transport.request.call_args.kwargs["body"]) + assert creation.get("model_version") == experiment_options.get("model_version") + transport.request.reset_mock() + items = [ + EvaluationItem(model_version=model) + for model in ["model-b", "model-c", "x" * 255, " model with spaces ", None] + ] + [EvaluationItem()] + invoke(experiment.upload_results, [result(item) for item in items]) + transport.request.assert_called_once() + assert transport.request.call_args.args == ( + "POST", + BASE + experiment.id + "/upload/", + ) + body = json.loads(transport.request.call_args.kwargs["body"]) + assert body["items"] == [item.to_dict() for item in items] + assert [item.get("model_version") for item in body["items"]] == [ + "model-b", + "model-c", + "x" * 255, + " model with spaces ", + None, + None, + ] + assert all("model_version" not in row for row in body["results"]) + assert all("payload" not in item for item in body["items"]) + if experiment_options is not None: + assert experiment.submission == creation + + +@pytest.mark.parametrize("model", ["", 1, True, [], {}, "x" * 256, "with\x00null"]) +def test_invalid_item_models_are_rejected_before_io(client, model): + _, transport = client + with pytest.raises(ValueError, match="model_version"): + EvaluationItem(model_version=model) + with pytest.raises(ValueError, match="model_version"): + EvaluationItem.from_dict({"id": str(uuid4()), "model_version": model}) + transport.request.assert_not_called() + + +@pytest.mark.parametrize( + "original_model,changed_model", + [("model-b", "model-c"), ("model-b", None), (None, "model-a")], +) +def test_conflicting_item_models_are_rejected_before_any_bulk_chunk( + client, monkeypatch, original_model, changed_model +): + evaluations, transport = client + monkeypatch.setattr(_client, "_MAX_RESULTS", 1) + item = EvaluationItem(model_version=original_model) + changed = EvaluationItem(id=item.id, model_version=changed_model) + results = [ + result(item), + EvaluationResult(item=changed, scorer_version_id=OTHER_VERSION_ID, value=True), + ] + with pytest.raises(ValueError, match="Conflicting"): + invoke(evaluations.resume_experiment(EXPERIMENT_ID).upload_results, results) + transport.request.assert_not_called() + + +@pytest.mark.parametrize( + "model_options", [{}, {"model_version": None}, {"model_version": "model-b"}] +) +def test_saved_item_models_replay_and_allow_another_scorer_by_uuid( + client, model_options +): + evaluations, transport = client + experiment = invoke( + evaluations.create_experiment, name="Models", model_version="model-a" + ) + item = EvaluationItem(**model_options) + original = result(item) + first = invoke(experiment.upload_results, [original]).chunks[0] + original_body = transport.request.call_args.kwargs["body"] + saved = item.to_dict() + saved.update(model_options) + restored_item = EvaluationItem.from_dict(saved) + restored = EvaluationResult.from_dict(original.to_dict(), item=restored_item) + second_scorer = EvaluationResult( + item=item, scorer_version_id=OTHER_VERSION_ID, value=True + ) + restored_second = EvaluationResult.from_dict( + second_scorer.to_dict(), item=restored_item + ) + saved["model_version"] = "changed" + assert restored_item.to_dict() == item.to_dict() + assert restored.item is restored_second.item is restored_item + assert restored.item_id == restored_second.item_id == item.id + transport.request.side_effect = lambda method, path, *, body: upload_response( + body, created=False + ) + replayed = invoke(experiment.upload_results, [restored]).chunks[0] + assert transport.request.call_args.kwargs["body"] == original_body + for accepted, replay in zip( + first.items + first.results, replayed.items + replayed.results + ): + assert replay.id == accepted.id + assert replay.accepted_at == accepted.accepted_at + assert replay.created is False + transport.request.reset_mock() + invoke( + experiment.upload_result, + item=item.id, + scorer_version_id=OTHER_VERSION_ID, + value=True, + ) + transport.request.assert_called_once() + body = json.loads(transport.request.call_args.kwargs["body"]) + assert body["items"] == [] + assert "model_version" not in body["results"][0] + + def test_real_result_count_boundary_splits_without_dropping_declarations(client): evaluations, transport = client results = [result() for _ in range(1001)] @@ -321,6 +448,57 @@ def test_utf8_byte_boundary_splits_below_request_limit(client): ) +def test_item_models_count_towards_the_encoded_request_limit(client, monkeypatch): + evaluations, transport = client + experiment = evaluations.resume_experiment(EXPERIMENT_ID) + items = [EvaluationItem(), EvaluationItem()] + invoke(experiment.upload_results, [result(item) for item in items]) + limit = len(transport.request.call_args.kwargs["body"]) + monkeypatch.setattr(_client, "_MAX_REQUEST_BYTES", limit) + transport.request.reset_mock() + modeled = [EvaluationItem(id=item.id, model_version="🙂" * 20) for item in items] + receipt = invoke(experiment.upload_results, [result(item) for item in modeled]) + assert len(receipt.chunks) == 2 + for call, item in zip(transport.request.call_args_list, modeled): + assert len(call.kwargs["body"]) <= limit + assert json.loads(call.kwargs["body"])["items"] == [item.to_dict()] + + +def test_shared_item_model_survives_partial_bulk_failure_and_recovery(client): + evaluations, transport = client + item = EvaluationItem(model_version="model-b") + results = [ + EvaluationResult( + item=item, scorer_version_id=uuid5(UUID(VERSION_ID), str(index)), value=True + ) + for index in range(1001) + ] + + def fail_second(method, path, *, body): + if transport.request.call_count == 2: + raise EvaluationAPIError(persistence="unknown") + return upload_response(body) + + transport.request.side_effect = fail_second + experiment = evaluations.resume_experiment(EXPERIMENT_ID) + with pytest.raises(BulkUploadError) as caught: + invoke(experiment.upload_results, results) + error = caught.value + assert len(error.completed.chunks[0].results) == 1000 + assert error.failed_index == 1000 + assert error.pending_results == (results[-1],) + assert error.failed_chunk["items"] == [item.to_dict()] + failed_body = transport.request.call_args.kwargs["body"] + error.failed_chunk["items"][0]["model_version"] = "changed" + assert error.failed_chunk["items"] == [item.to_dict()] + transport.request.side_effect = respond + invoke(experiment.upload_results, error.pending_results) + assert transport.request.call_args.kwargs["body"] == failed_body + assert transport.request.call_count == 3 + for call in transport.request.call_args_list: + assert json.loads(call.kwargs["body"])["items"] == [item.to_dict()] + + def test_shared_item_is_redeclared_in_each_independently_replayable_chunk( client, monkeypatch ): diff --git a/posthog/test/ai/test_evaluations_datasets.py b/posthog/test/ai/test_evaluations_datasets.py index e90d3400..dfc40597 100644 --- a/posthog/test/ai/test_evaluations_datasets.py +++ b/posthog/test/ai/test_evaluations_datasets.py @@ -648,6 +648,57 @@ def test_dataset_to_execution_copies_payload_and_keeps_trials_independent(client EvaluationItem.from_dataset_item(replace(case, archived=True)) +def test_dataset_executions_keep_explicit_models_and_never_infer_them(client): + evaluations, transport = client + transport.request.return_value = item_response( + metadata={"model_version": "source-model"} + ) + case = invoke(evaluations.datasets.get_item, ITEM_ID) + transport.request.reset_mock() + items = [ + EvaluationItem.from_dataset_item(case, output="Paris", model_version="model-b"), + EvaluationItem.from_dataset_item(case, output="Lyon", model_version="model-c"), + EvaluationItem.from_dataset_item(case), + EvaluationItem.from_dataset_item(case, model_version=None), + ] + assert len({item.id for item in items}) == 4 + for item in items: + data = item.to_dict() + assert data["case_key"] == "capital-france" + assert data["dataset_item_version_id"] == VERSION_ID + assert data["payload"]["metadata"] == {"model_version": "source-model"} + assert EvaluationItem.from_dict(data).to_dict() == data + for invalid in ["", 1, True, "x" * 256]: + with pytest.raises(ValueError, match="model_version"): + EvaluationItem.from_dataset_item(case, model_version=invalid) + transport.request.assert_not_called() + transport.request.side_effect = respond_to_experiment + experiment = invoke( + evaluations.create_experiment, name="Models", model_version="model-a" + ) + invoke( + experiment.upload_results, + [ + EvaluationResult(item=item, scorer_version_id=OTHER_ITEM_ID, value=True) + for item in items + ], + ) + body = json.loads(transport.request.call_args.kwargs["body"]) + assert body["items"] == [item.to_dict() for item in items] + assert [item.get("model_version") for item in body["items"]] == [ + "model-b", + "model-c", + None, + None, + ] + assert [item["payload"].get("output") for item in body["items"]] == [ + "Paris", + "Lyon", + None, + None, + ] + + def test_dataset_scorer_experiment_flow_pins_both_versions_and_reuses_execution(client): evaluations, transport = client bodies = [] diff --git a/posthog/test/ai/test_evaluations_transport.py b/posthog/test/ai/test_evaluations_transport.py index 3f62fe93..8f247f76 100644 --- a/posthog/test/ai/test_evaluations_transport.py +++ b/posthog/test/ai/test_evaluations_transport.py @@ -12,9 +12,16 @@ import pytest import requests +from posthog.ai.evaluations import ( + AsyncOfflineEvaluations, + EvaluationItem, + OfflineEvaluations, +) from posthog.ai.evaluations._errors import EvaluationAPIError from posthog.ai.evaluations._transport import AsyncTransport, SyncTransport +from .test_evaluations import EXPERIMENT_ID, VERSION_ID, invoke, upload_response + PATH = "/api/projects/123/ai_observability/offline_experiments/" BODY = b'{"id":"stable-id","name":"evaluation data"}' @@ -102,6 +109,90 @@ def test_lost_response_retries_identical_body_and_identity(transport): sleep.assert_called_once() +@pytest.mark.parametrize("asynchronous", [False, True], ids=["sync", "async"]) +@pytest.mark.parametrize( + "original_model,changed_model", + [("model-b", "model-c"), ("model-b", None), (None, "model-a")], +) +def test_item_models_survive_transport_retries_and_content_conflicts( + asynchronous, original_model, changed_model +): + evaluations = (AsyncOfflineEvaluations if asynchronous else OfflineEvaluations)( + project_id=123, secret_key="phx_test_secret" + ) + experiment = evaluations.resume_experiment(EXPERIMENT_ID) + item = EvaluationItem(model_version=original_model) + conflict = { + "code": "content_conflict", + "detail": "Item content differs from its accepted declaration.", + "attr": "items.0.model_version", + } + attempts = [] + + def respond(method, url, **kwargs): + body = kwargs["content" if asynchronous else "data"] + attempts.append((method, url, body)) + if len(attempts) == 1: + raise ( + httpx.ReadTimeout("lost response") + if asynchronous + else requests.ReadTimeout() + ) + status, payload = ( + (201, upload_response(body)) if len(attempts) == 2 else (409, conflict) + ) + return ( + httpx.Response(status, json=payload) + if asynchronous + else response(status, payload) + ) + + target = ( + evaluations._transport._client + if asynchronous + else evaluations._transport._session + ) + try: + with ( + patch.object(target, "request", side_effect=respond), + patch("posthog.ai.evaluations._transport.time.sleep"), + patch( + "posthog.ai.evaluations._transport.asyncio.sleep", + new_callable=AsyncMock, + ), + ): + receipt = invoke( + experiment.upload_result, + item=item, + scorer_version_id=VERSION_ID, + value=True, + ) + assert receipt.items[0].id == item.id + assert len(attempts) == 2 + assert attempts[0] == attempts[1] + assert json.loads(attempts[0][2])["items"] == [item.to_dict()] + changed = EvaluationItem(id=item.id, model_version=changed_model) + with pytest.raises(EvaluationAPIError) as caught: + invoke( + experiment.upload_result, + item=changed, + scorer_version_id=VERSION_ID, + value=True, + ) + assert caught.value.status == 409 + assert caught.value.code == "content_conflict" + assert caught.value.response == conflict + assert caught.value.persistence == "rejected" + assert len(attempts) == 3 + assert json.loads(attempts[2][2])["items"] == [changed.to_dict()] + assert all( + method == "POST" and url.endswith(f"/{EXPERIMENT_ID}/upload/") + for method, url, _ in attempts + ) + finally: + invoke(evaluations.aclose if asynchronous else evaluations.close) + + @pytest.mark.parametrize("failure", [requests.ReadTimeout(), response(503)]) def test_non_idempotent_mutation_never_replays(transport, failure): with ( diff --git a/references/public_api_snapshot.txt b/references/public_api_snapshot.txt index 67cc7c4e..4d3b41e1 100644 --- a/references/public_api_snapshot.txt +++ b/references/public_api_snapshot.txt @@ -1201,7 +1201,7 @@ class posthog.ai.evaluations.DatasetRevisionPage(count: int, results: tuple[Data class posthog.ai.evaluations.DatasetSnapshot(dataset: Dataset, revision: int, revision_id: str, _datasets: Datasets) class posthog.ai.evaluations.Datasets(transport: SyncTransport, project_id: int) class posthog.ai.evaluations.EvaluationAPIError(*, status: int | None = None, code: str | None = None, detail: Any = None, attr: str | None = None, errors: Any = None, response: dict[str, Any] | None = None, retry_after: float | None = None, persistence: Literal['rejected', 'unknown', 'not_sent'] = 'unknown', submission: dict[str, Any] | None = None) -class posthog.ai.evaluations.EvaluationItem(*, id: str | UUID | None = None, input: JSONValue | _Unset = _UNSET, output: JSONValue | _Unset = _UNSET, expected_output: JSONValue | _Unset = _UNSET, metadata: dict[str, JSONValue] | None | _Unset = _UNSET, case_key: str | None = None, trial: str | None = None, dataset_item_identifier: str | None = None, dataset_item_version_identifier: str | None = None, dataset_item_version_id: str | UUID | None = None, application_trace_id: str | None = None, payload: dict[str, JSONValue] | _Unset = _UNSET) +class posthog.ai.evaluations.EvaluationItem(*, id: str | UUID | None = None, input: JSONValue | _Unset = _UNSET, output: JSONValue | _Unset = _UNSET, expected_output: JSONValue | _Unset = _UNSET, metadata: dict[str, JSONValue] | None | _Unset = _UNSET, case_key: str | None = None, trial: str | None = None, dataset_item_identifier: str | None = None, dataset_item_version_identifier: str | None = None, dataset_item_version_id: str | UUID | None = None, application_trace_id: str | None = None, model_version: str | None = None, payload: dict[str, JSONValue] | _Unset = _UNSET) class posthog.ai.evaluations.EvaluationResult(*, item: EvaluationItem | str | UUID, scorer_version_id: str | UUID, value: ScoreValue | None = None, status: ResultStatus = 'ok', error_code: str | None = None, reasoning: str | None | _Unset = _UNSET, error_message: str | None | _Unset = _UNSET, metadata: dict[str, JSONValue] | None | _Unset = _UNSET, evaluator_trace_id: str | None = None, evaluated_at: datetime | str | None = None, payload: dict[str, JSONValue] | _Unset = _UNSET) class posthog.ai.evaluations.Experiment(transport: SyncTransport, project_id: int, id: str, *, receipt: ExperimentReceipt | None = None, submission: bytes | None = None) class posthog.ai.evaluations.ExperimentOptions @@ -1617,7 +1617,7 @@ method posthog.ai.evaluations.Datasets.restore_item(item_id: str | UUID, *, base method posthog.ai.evaluations.Datasets.snapshot(dataset_id: str | UUID, *, revision: int | None = None) method posthog.ai.evaluations.Datasets.update(dataset_id: str | UUID, *, name: str | _Unset = _UNSET, description: str | _Unset = _UNSET, metadata: dict[str, JSONValue] | _Unset = _UNSET, tags: Sequence[str] | _Unset = _UNSET) method posthog.ai.evaluations.Datasets.update_item(item_id: str | UUID, *, base_version: int, input: JSONValue | _Unset = _UNSET, expected_output: JSONValue | _Unset = _UNSET, metadata: dict[str, JSONValue] | _Unset = _UNSET) -method posthog.ai.evaluations.EvaluationItem.from_dataset_item(item: DatasetItem, *, output: JSONValue | _Unset = _UNSET, id: str | UUID | None = None, case_key: str | None = None, trial: str | None = None, application_trace_id: str | None = None) +method posthog.ai.evaluations.EvaluationItem.from_dataset_item(item: DatasetItem, *, output: JSONValue | _Unset = _UNSET, id: str | UUID | None = None, case_key: str | None = None, trial: str | None = None, application_trace_id: str | None = None, model_version: str | None = None) method posthog.ai.evaluations.EvaluationItem.from_dict(data: Mapping[str, Any]) method posthog.ai.evaluations.EvaluationItem.to_dict() method posthog.ai.evaluations.EvaluationResult.from_dict(data: Mapping[str, Any], *, item: EvaluationItem | None = None) From 5b762b742e249f25fa6f8622bb982078e9f8bbdd Mon Sep 17 00:00:00 2001 From: "releaser-posthog-python[bot]" <262414804+releaser-posthog-python[bot]@users.noreply.github.com> Date: Tue, 27 Oct 2026 13:05:02 +0000 Subject: [PATCH 23/60] chore: Release v7.76.0 [skip ci] (cherry picked from commit 7d15127fd4e3aeac72bb1af59f49d96c1c81be7e) --- .sampo/changesets/jovial-shaman-kullervo.md | 5 ----- openfeature-provider/CHANGELOG.md | 6 ++++++ openfeature-provider/pyproject.toml | 2 +- posthog/CHANGELOG.md | 6 ++++++ posthog/version.py | 2 +- pyproject.toml | 2 +- uv.lock | 4 ++-- 7 files changed, 17 insertions(+), 10 deletions(-) delete mode 100644 .sampo/changesets/jovial-shaman-kullervo.md diff --git a/.sampo/changesets/jovial-shaman-kullervo.md b/.sampo/changesets/jovial-shaman-kullervo.md deleted file mode 100644 index 1138e5cc..00000000 --- a/.sampo/changesets/jovial-shaman-kullervo.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -pypi/posthog: minor ---- - -Add optional per-item model_version to offline evaluations and dataset conversion, preserving model attribution across serialization, retries, and recovery. diff --git a/openfeature-provider/CHANGELOG.md b/openfeature-provider/CHANGELOG.md index 5f930ed7..599b78d5 100644 --- a/openfeature-provider/CHANGELOG.md +++ b/openfeature-provider/CHANGELOG.md @@ -4,6 +4,12 @@ All notable changes to `openfeature-provider-posthog` are documented here. This file is maintained by [Sampo](https://github.com/bruits/sampo) from changesets in `.sampo/changesets/` that target `pypi/openfeature-provider-posthog`. +## 0.1.126 — 2026-10-27 + +### Patch changes + +- Updated dependencies: posthog@7.76.0 + ## 0.1.125 — 2026-10-23 ### Patch changes diff --git a/openfeature-provider/pyproject.toml b/openfeature-provider/pyproject.toml index 7de2d5ca..96b2e73e 100644 --- a/openfeature-provider/pyproject.toml +++ b/openfeature-provider/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "openfeature-provider-posthog" -version = "0.1.125" +version = "0.1.126" description = "Official PostHog provider for the OpenFeature Python SDK." readme = "README.md" authors = [{ name = "PostHog", email = "engineering@posthog.com" }] diff --git a/posthog/CHANGELOG.md b/posthog/CHANGELOG.md index 32d74af1..de469630 100644 --- a/posthog/CHANGELOG.md +++ b/posthog/CHANGELOG.md @@ -1,5 +1,11 @@ # posthog +## 7.76.0 — 2026-10-27 + +### Minor changes + +- [284340d](https://github.com/posthog/posthog-python/commit/284340d6ba6df08f68c27eac750b13f3ec89ab19) Add optional per-item model_version to offline evaluations and dataset conversion, preserving model attribution across serialization, retries, and recovery. — Thanks @Radu-Raicea! + ## 7.75.11 — 2026-10-23 ### Patch changes diff --git a/posthog/version.py b/posthog/version.py index 0d256d5f..7fae9282 100644 --- a/posthog/version.py +++ b/posthog/version.py @@ -1 +1 @@ -VERSION = "7.75.11" +VERSION = "7.76.0" diff --git a/pyproject.toml b/pyproject.toml index b3003b75..81cac583 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "posthog" -version = "7.75.11" +version = "7.76.0" description = "Integrate PostHog into any python application." authors = [{ name = "PostHog", email = "engineering@posthog.com" }] maintainers = [{ name = "PostHog", email = "engineering@posthog.com" }] diff --git a/uv.lock b/uv.lock index 85c80de1..eba651da 100644 --- a/uv.lock +++ b/uv.lock @@ -2656,7 +2656,7 @@ wheels = [ [[package]] name = "openfeature-provider-posthog" -version = "0.1.125" +version = "0.1.126" source = { editable = "openfeature-provider" } dependencies = [ { name = "openfeature-sdk" }, @@ -3045,7 +3045,7 @@ wheels = [ [[package]] name = "posthog" -version = "7.75.11" +version = "7.76.0" source = { editable = "." } dependencies = [ { name = "distro" }, From 922a3b3470b6182a24e17e1c85c727993b233875 Mon Sep 17 00:00:00 2001 From: "releaser-posthog-python[bot]" <262414804+releaser-posthog-python[bot]@users.noreply.github.com> Date: Tue, 27 Oct 2026 13:05:10 +0000 Subject: [PATCH 24/60] Update generated references (cherry picked from commit 7833e95266da6130bf41028623f4a5e69a950d71) --- .../posthog-python-references-7.76.0.json | 3548 +++++++++++++++++ .../posthog-python-references-latest.json | 2 +- 2 files changed, 3549 insertions(+), 1 deletion(-) create mode 100644 references/posthog-python-references-7.76.0.json diff --git a/references/posthog-python-references-7.76.0.json b/references/posthog-python-references-7.76.0.json new file mode 100644 index 00000000..9df82cc5 --- /dev/null +++ b/references/posthog-python-references-7.76.0.json @@ -0,0 +1,3548 @@ +{ + "id": "posthog-python", + "hogRef": "0.3", + "info": { + "version": "7.76.0", + "id": "posthog-python", + "title": "PostHog Python SDK", + "description": "Integrate PostHog into any python application.", + "slugPrefix": "posthog-python", + "specUrl": "https://github.com/PostHog/posthog-python" + }, + "types": [ + { + "id": "FeatureFlag", + "name": "FeatureFlag", + "path": "posthog.types.FeatureFlag", + "properties": [ + { + "name": "key", + "type": "str", + "description": "Field: key" + }, + { + "name": "enabled", + "type": "bool", + "description": "Field: enabled" + }, + { + "name": "variant", + "type": "Optional[str]", + "description": "Field: variant" + }, + { + "name": "reason", + "type": "Optional[FlagReason]", + "description": "Field: reason" + }, + { + "name": "metadata", + "type": "Union[FlagMetadata, LegacyFlagMetadata]", + "description": "Field: metadata" + } + ], + "example": "" + }, + { + "id": "FeatureFlagResult", + "name": "FeatureFlagResult", + "path": "posthog.types.FeatureFlagResult", + "properties": [ + { + "name": "key", + "type": "str", + "description": "Field: key" + }, + { + "name": "enabled", + "type": "bool", + "description": "Field: enabled" + }, + { + "name": "variant", + "type": "Optional[str]", + "description": "Field: variant" + }, + { + "name": "payload", + "type": "Optional[Any]", + "description": "Field: payload" + }, + { + "name": "reason", + "type": "Optional[str]", + "description": "Field: reason" + } + ], + "example": "" + }, + { + "id": "FlagMetadata", + "name": "FlagMetadata", + "path": "posthog.types.FlagMetadata", + "properties": [ + { + "name": "id", + "type": "int", + "description": "Field: id" + }, + { + "name": "payload", + "type": "Optional[str]", + "description": "Field: payload" + }, + { + "name": "version", + "type": "int", + "description": "Field: version" + }, + { + "name": "description", + "type": "str", + "description": "Field: description" + }, + { + "name": "has_experiment", + "type": "Optional[bool]", + "description": "Field: has_experiment" + } + ], + "example": "" + }, + { + "id": "FlagReason", + "name": "FlagReason", + "path": "posthog.types.FlagReason", + "properties": [ + { + "name": "code", + "type": "str", + "description": "Field: code" + }, + { + "name": "condition_index", + "type": "Optional[int]", + "description": "Field: condition_index" + }, + { + "name": "description", + "type": "str", + "description": "Field: description" + } + ], + "example": "" + }, + { + "id": "FlagsAndPayloads", + "name": "FlagsAndPayloads", + "path": "posthog.types.FlagsAndPayloads", + "properties": [ + { + "name": "featureFlags", + "type": "Optional[dict[str, Union[bool, str]]]", + "description": "Field: featureFlags" + }, + { + "name": "featureFlagPayloads", + "type": "Optional[dict[str, Any]]", + "description": "Field: featureFlagPayloads" + } + ], + "example": "" + }, + { + "id": "FlagsResponse", + "name": "FlagsResponse", + "path": "posthog.types.FlagsResponse", + "properties": [ + { + "name": "flags", + "type": "dict[str, FeatureFlag]", + "description": "Field: flags" + }, + { + "name": "errorsWhileComputingFlags", + "type": "bool", + "description": "Field: errorsWhileComputingFlags" + }, + { + "name": "requestId", + "type": "str", + "description": "Field: requestId" + }, + { + "name": "quotaLimit", + "type": "Optional[list[str]]", + "description": "Field: quotaLimit" + }, + { + "name": "evaluatedAt", + "type": "Optional[int]", + "description": "Field: evaluatedAt" + }, + { + "name": "minimalFlagCalledEvents", + "type": "bool", + "description": "Field: minimalFlagCalledEvents" + } + ], + "example": "" + }, + { + "id": "LegacyFlagMetadata", + "name": "LegacyFlagMetadata", + "path": "posthog.types.LegacyFlagMetadata", + "properties": [ + { + "name": "payload", + "type": "Any", + "description": "Field: payload" + } + ], + "example": "" + }, + { + "id": "SendFeatureFlagsOptions", + "name": "SendFeatureFlagsOptions", + "path": "posthog.types.SendFeatureFlagsOptions", + "properties": [ + { + "name": "should_send", + "type": "bool", + "description": "Field: should_send" + }, + { + "name": "only_evaluate_locally", + "type": "Optional[bool]", + "description": "Field: only_evaluate_locally" + }, + { + "name": "person_properties", + "type": "Optional[dict[str, Any]]", + "description": "Field: person_properties" + }, + { + "name": "group_properties", + "type": "Optional[dict[str, dict[str, Any]]]", + "description": "Field: group_properties" + }, + { + "name": "flag_keys_filter", + "type": "Optional[list[str]]", + "description": "Field: flag_keys_filter" + } + ], + "example": "" + }, + { + "id": "OptionalCaptureArgs", + "name": "OptionalCaptureArgs", + "path": "posthog.args.OptionalCaptureArgs", + "properties": [ + { + "name": "distinct_id", + "type": "NotRequired[Union[Number, str, UUID, int, any]]", + "description": "Field: distinct_id" + }, + { + "name": "properties", + "type": "NotRequired[Optional[dict[str, Any]]]", + "description": "Field: properties" + }, + { + "name": "timestamp", + "type": "NotRequired[Union[datetime, str, any]]", + "description": "Field: timestamp" + }, + { + "name": "uuid", + "type": "NotRequired[Union[str, UUID, any]]", + "description": "Field: uuid" + }, + { + "name": "groups", + "type": "NotRequired[Optional[dict[str, str]]]", + "description": "Field: groups" + }, + { + "name": "flags", + "type": "NotRequired[Optional[ForwardRef('FeatureFlagEvaluations')]]", + "description": "Field: flags" + }, + { + "name": "send_feature_flags", + "type": "NotRequired[Union[bool, SendFeatureFlagsOptions, any]]", + "description": "Field: send_feature_flags" + }, + { + "name": "disable_geoip", + "type": "NotRequired[Optional[bool]]", + "description": "Field: disable_geoip" + }, + { + "name": "_property_allowlist", + "type": "NotRequired[Optional[frozenset[str]]]", + "description": "Field: _property_allowlist" + } + ], + "example": "" + }, + { + "id": "OptionalSetArgs", + "name": "OptionalSetArgs", + "path": "posthog.args.OptionalSetArgs", + "properties": [ + { + "name": "distinct_id", + "type": "NotRequired[Union[Number, str, UUID, int, any]]", + "description": "Field: distinct_id" + }, + { + "name": "properties", + "type": "NotRequired[Optional[dict[str, Any]]]", + "description": "Field: properties" + }, + { + "name": "timestamp", + "type": "NotRequired[Union[datetime, str, any]]", + "description": "Field: timestamp" + }, + { + "name": "uuid", + "type": "NotRequired[Union[str, UUID, any]]", + "description": "Field: uuid" + }, + { + "name": "disable_geoip", + "type": "NotRequired[Optional[bool]]", + "description": "Field: disable_geoip" + } + ], + "example": "" + }, + { + "id": "SendFeatureFlagsOptions", + "name": "SendFeatureFlagsOptions", + "path": "posthog.types.SendFeatureFlagsOptions", + "properties": [ + { + "name": "should_send", + "type": "bool", + "description": "Field: should_send" + }, + { + "name": "only_evaluate_locally", + "type": "Optional[bool]", + "description": "Field: only_evaluate_locally" + }, + { + "name": "person_properties", + "type": "Optional[dict[str, Any]]", + "description": "Field: person_properties" + }, + { + "name": "group_properties", + "type": "Optional[dict[str, dict[str, Any]]]", + "description": "Field: group_properties" + }, + { + "name": "flag_keys_filter", + "type": "Optional[list[str]]", + "description": "Field: flag_keys_filter" + } + ], + "example": "" + } + ], + "classes": [ + { + "id": "PostHog", + "title": "PostHog", + "description": "This is the SDK reference for the PostHog Python SDK. You can learn more about example usage in the [Python SDK documentation](/docs/libraries/python). You can also follow [Flask](/docs/libraries/flask) and [Django](/docs/libraries/django) guides to integrate PostHog into your project. For long-running applications, create one client during application startup and reuse it for the lifetime of the process. This keeps background queues predictable and makes shutdown flushing straightforward. Multiple clients are still supported for intentional multi-project or multi-host setups.", + "functions": [ + { + "id": "__init__", + "title": "Client", + "description": "Initialize a new PostHog client instance.", + "details": "", + "category": "Initialization", + "params": [ + { + "name": "project_api_key", + "description": "PostHog project API key/token.", + "isOptional": true, + "type": "str" + }, + { + "name": "host", + "description": "PostHog host. Defaults to the US ingestion endpoint when not set. App hosts such as ``https://us.posthog.com`` are mapped to the corresponding ingestion host.", + "isOptional": false, + "type": "any" + }, + { + "name": "debug", + "description": "Enable verbose SDK logging and re-raise errors from public API methods.", + "isOptional": false, + "type": "bool" + }, + { + "name": "max_queue_size", + "description": "Maximum number of events buffered before upload.", + "isOptional": false, + "type": "int" + }, + { + "name": "send", + "description": "If False, queueing succeeds but events are not sent.", + "isOptional": false, + "type": "bool" + }, + { + "name": "on_error", + "description": "Optional callback invoked by background consumers when an upload fails. Keep it short and non-blocking. Calling lifecycle methods directly is safe and deferred, but do not start another thread or task that calls ``flush()``, ``join()``, or ``shutdown()`` and then wait for it from the callback.", + "isOptional": false, + "type": "any" + }, + { + "name": "flush_at", + "description": "Number of queued events that triggers a batch upload.", + "isOptional": false, + "type": "int" + }, + { + "name": "flush_interval", + "description": "Maximum seconds a background consumer waits before flushing a partial batch.", + "isOptional": false, + "type": "float" + }, + { + "name": "gzip", + "description": "Whether to gzip event upload payloads.", + "isOptional": false, + "type": "bool" + }, + { + "name": "max_retries", + "description": "Number of upload retries. Values below 0 are treated as 0.", + "isOptional": false, + "type": "int" + }, + { + "name": "sync_mode", + "description": "If True, send each event synchronously instead of using background worker threads. This blocks the calling thread; in asyncio applications such as FastAPI, use ``AsyncPosthog`` instead.", + "isOptional": false, + "type": "bool" + }, + { + "name": "timeout", + "description": "HTTP request timeout in seconds for event uploads.", + "isOptional": false, + "type": "float" + }, + { + "name": "thread", + "description": "Number of background consumer threads.", + "isOptional": false, + "type": "int" + }, + { + "name": "poll_interval", + "description": "Seconds between local feature flag definition refreshes.", + "isOptional": false, + "type": "int" + }, + { + "name": "personal_api_key", + "description": "Deprecated alias for ``secret_key``. Still honored for backwards compatibility; prefer ``secret_key``, which also accepts a Project Secret API Key.", + "isOptional": false, + "type": "any" + }, + { + "name": "disabled", + "description": "If True, disable captures and API requests. Useful in tests.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable server-side GeoIP enrichment. Defaults to True.", + "isOptional": false, + "type": "bool" + }, + { + "name": "is_server", + "description": "Whether events are emitted from a server-side runtime. Defaults to True; set to False when using the SDK as a client/CLI so the device OS is attributed to the person normally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "historical_migration", + "description": "Mark events as historical migration imports.", + "isOptional": false, + "type": "bool" + }, + { + "name": "feature_flags_request_timeout_seconds", + "description": "Timeout in seconds for feature flag and remote config requests.", + "isOptional": false, + "type": "float" + }, + { + "name": "feature_flags_request_max_retries", + "description": "Number of retries for feature flag requests after network, transport, or timeout failures. Defaults to 1. Set to 0 to disable retries.", + "isOptional": false, + "type": "int" + }, + { + "name": "super_properties", + "description": "Properties merged into every captured event.", + "isOptional": false, + "type": "any" + }, + { + "name": "enable_exception_autocapture", + "description": "Automatically capture uncaught exceptions.", + "isOptional": false, + "type": "bool" + }, + { + "name": "log_captured_exceptions", + "description": "Also log exceptions captured by error tracking.", + "isOptional": false, + "type": "bool" + }, + { + "name": "project_root", + "description": "Root path used to determine in-app stack frames for captured exceptions. Defaults to the current working directory.", + "isOptional": false, + "type": "any" + }, + { + "name": "privacy_mode", + "description": "For AI observability, capture usage metadata without prompt inputs or outputs.", + "isOptional": false, + "type": "bool" + }, + { + "name": "before_send", + "description": "Optional callback that can modify or drop events before upload. Return ``None`` to drop an event.", + "isOptional": false, + "type": "any" + }, + { + "name": "flag_fallback_cache_url", + "description": "Optional feature flag fallback cache URL, such as ``memory://local/?ttl=300&size=10000`` or a Redis URL.", + "isOptional": false, + "type": "any" + }, + { + "name": "enable_local_evaluation", + "description": "Whether to poll feature flag definitions for local evaluation when a personal API key is configured.", + "isOptional": false, + "type": "bool" + }, + { + "name": "flag_definition_cache_provider", + "description": "Optional external cache provider for sharing feature flag definitions across workers.", + "isOptional": true, + "type": "FlagDefinitionCacheProvider" + }, + { + "name": "capture_exception_code_variables", + "description": "Capture local variable values on exception stack frames.", + "isOptional": false, + "type": "bool" + }, + { + "name": "code_variables_mask_patterns", + "description": "Variable-name patterns to mask when capturing code variables.", + "isOptional": false, + "type": "any" + }, + { + "name": "code_variables_ignore_patterns", + "description": "Variable-name patterns to omit when capturing code variables.", + "isOptional": false, + "type": "any" + }, + { + "name": "code_variables_mask_url_credentials", + "description": "Scrub credentials embedded in URLs/DSNs (e.g. ``user:pass@host``) from captured code variables, regardless of the surrounding variable name. Defaults to True.", + "isOptional": false, + "type": "any" + }, + { + "name": "code_variables_detect_secrets", + "description": "Last-resort entropy-based detection that redacts high-entropy secret-looking values (API keys, tokens, strong passwords) sitting in innocuously-named variables, after the name and URL checks. Skips structured ids (UUIDs, ObjectIds, hashes). Defaults to True.", + "isOptional": false, + "type": "any" + }, + { + "name": "in_app_modules", + "description": "Module/package prefixes treated as in-app frames in captured exceptions.", + "isOptional": false, + "type": "UnionType[list[str], any]" + }, + { + "name": "enable_exception_autocapture_rate_limiting", + "description": "Rate limit autocaptured exceptions client-side with a token bucket per exception type. Disabled by default.", + "isOptional": false, + "type": "bool" + }, + { + "name": "exception_autocapture_bucket_size", + "description": "Maximum burst of autocaptured exceptions allowed per exception type (token bucket size, clamped to 0-100).", + "isOptional": false, + "type": "int" + }, + { + "name": "exception_autocapture_refill_rate", + "description": "Tokens restored per refill interval for each exception type's bucket.", + "isOptional": false, + "type": "int" + }, + { + "name": "exception_autocapture_refill_interval_seconds", + "description": "Seconds between token refills for autocaptured exception rate limiting.", + "isOptional": false, + "type": "int" + }, + { + "name": "capture_mode", + "description": "Capture wire protocol to use. Defaults to ``CaptureMode.V0`` (legacy ``/batch/``). Set ``CaptureMode.V1`` (or pass the string ``\"v1\"``) to opt into ``/i/v1/analytics/events``. When omitted, the ``POSTHOG_CAPTURE_MODE`` env var is consulted, then ``V0``.", + "isOptional": false, + "type": "CaptureMode" + }, + { + "name": "capture_compression", + "description": "Request-body compression for capture-v1 uploads (ignored in V0, which uses ``gzip``). ``CaptureCompression.GZIP`` or ``DEFLATE`` (or the strings ``\"gzip\"``/``\"deflate\"``). When omitted, the ``POSTHOG_CAPTURE_COMPRESSION`` env var is consulted, then the legacy ``gzip`` flag, then no compression.", + "isOptional": false, + "type": "CaptureCompression" + }, + { + "name": "secret_key", + "description": "A Personal API Key or Project Secret API Key, used to authenticate local feature flag evaluation, remote config payloads, and decrypted flag payloads. Example:: posthog.Client(project_api_key, secret_key=\"phx_...\")", + "isOptional": false, + "type": "any" + }, + { + "name": "metrics", + "description": "", + "isOptional": true, + "type": "dict" + }, + { + "name": "enable_full_ai_capture", + "description": "Route PostHog AI wrapper events through the dedicated AI capture endpoint and capture full AI content: skips string truncation and passes media (base64/data URIs) through unredacted. ``privacy_mode`` always wins. Defaults to False.", + "isOptional": false, + "type": "bool" + }, + { + "name": "capture_trace_context", + "description": "When OpenTelemetry is installed and a valid span is active at capture time, add its trace and span IDs as ``$trace_id`` and ``$span_id`` properties to events captured with ``capture()`` and ``capture_ai()``, so they can be correlated with backend traces. Explicit ``$trace_id``/``$span_id`` values passed in ``properties`` win. Exception events (``capture_exception``) always attach these IDs regardless of this setting. Defaults to False.", + "isOptional": false, + "type": "bool" + }, + { + "name": "_use_ai_lane", + "description": "", + "isOptional": false, + "type": "bool" + }, + { + "name": "_enable_multimodal_capture", + "description": "", + "isOptional": false, + "type": "bool" + }, + { + "name": "traces", + "description": "Config dict for distributed tracing: ``service_name``, ``service_version``, ``environment``, ``resource_attributes``, ``flush_interval`` (5 s), ``max_queue_size`` (2048), ``max_export_batch_size`` (512), ``max_live_spans`` (10000), ``max_span_age`` (3600 s), ``max_attributes_per_span`` (128), ``max_events_per_span`` (128), ``max_attribute_value_length`` (8192). ``before_span_send`` is a callable, or a list run in order, that receives each finished span as a dict (``trace_id``, ``span_id`` and ``parent_span_id`` are read-only) and returns it, edited, or ``None`` to drop it; a hook that raises drops the span. Tracing is off until this is provided. Spans export on a background timer even with ``sync_mode``; serverless handlers should call ``flush()`` before returning. Defaults to None.", + "isOptional": true, + "type": "dict" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import Posthog\n\nposthog = Posthog('', host='')" + } + ] + }, + { + "id": "alias", + "title": "alias", + "description": "Create an alias between two distinct IDs.", + "details": "", + "category": "Identification", + "params": [ + { + "name": "previous_id", + "description": "The previous distinct ID. Required - the call is dropped with a warning if it is missing or empty.", + "isOptional": true, + "type": "Number" + }, + { + "name": "distinct_id", + "description": "The new distinct ID to alias to. Falls back to the context distinct ID; the call is dropped with a warning if neither is available.", + "isOptional": true, + "type": "str" + }, + { + "name": "timestamp", + "description": "The timestamp of the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC.", + "isOptional": false, + "type": "datetime" + }, + { + "name": "uuid", + "description": "A unique identifier for the event. If provided, it must be a valid UUID string or uuid.UUID instance; invalid values are ignored and replaced with a newly generated UUID.", + "isOptional": true, + "type": "str" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this event.", + "isOptional": true, + "type": "bool" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.alias(previous_id='distinct_id', distinct_id='alias_id')" + } + ] + }, + { + "id": "capture", + "title": "capture", + "description": "Captures an event manually. [Learn about capture best practices](https://posthog.com/docs/product-analytics/capture-events)", + "details": "", + "category": "Capture", + "params": [ + { + "name": "event", + "description": "The event name to capture.", + "isOptional": true, + "type": "str" + }, + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalCaptureArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Anonymous event", + "code": "# Anonymous event\nposthog.capture('some-anon-event')" + }, + { + "id": "example_2", + "name": "Context usage", + "code": "# Context usage\nfrom posthog import identify_context, new_context\nwith new_context():\n identify_context('distinct_id_of_the_user')\n posthog.capture('user_signed_up')\n posthog.capture('user_logged_in')\n posthog.capture('some-custom-action', distinct_id='distinct_id_of_the_user')" + }, + { + "id": "example_3", + "name": "Set event properties", + "code": "# Set event properties\nposthog.capture(\n \"user_signed_up\",\n distinct_id=\"distinct_id_of_the_user\",\n properties={\n \"login_type\": \"email\",\n \"is_free_trial\": \"true\"\n }\n)" + }, + { + "id": "example_4", + "name": "Page view event", + "code": "# Page view event\nposthog.capture('$pageview', distinct_id=\"distinct_id_of_the_user\", properties={'$current_url': 'https://example.com'})" + } + ] + }, + { + "id": "capture_ai", + "title": "capture_ai", + "description": "Capture an AI event on the dedicated AI capture endpoint. Beta: the signature is stable; operational limits (per-event size cap, batching, endpoint) may change without notice. Takes the same arguments and returns the same value as `capture()`: the event UUID, or None when the event was not admitted (disabled client, or dropped by `before_send`). The event is queued on an isolated AI lane with its own consumer pool and a higher per-event size cap, posting to the dedicated AI ingestion endpoint. The payload is sent as given \u2014 no redaction or truncation is applied here.", + "details": "", + "category": "Capture", + "params": [ + { + "name": "event", + "description": "", + "isOptional": true, + "type": "str" + }, + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalCaptureArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + } + }, + { + "id": "capture_exception", + "title": "capture_exception", + "description": "Capture an exception for error tracking. When OpenTelemetry is installed and a valid span is active, its trace and span IDs are added as ``$trace_id`` and ``$span_id`` event properties.", + "details": "", + "category": "Error Tracking", + "params": [ + { + "name": "exception", + "description": "The exception to capture.", + "isOptional": true, + "type": "BaseException" + }, + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalCaptureArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "try:\n # Some code that might fail\n pass\nexcept Exception as e:\n posthog.capture_exception(e, 'user_distinct_id', properties=additional_properties)" + } + ] + }, + { + "id": "evaluate_flags", + "title": "evaluate_flags", + "description": "Evaluate all feature flags for a user in a single call and return a :class:`FeatureFlagEvaluations` snapshot. Branch on ``.is_enabled()`` / ``.get_flag()`` and pass the same snapshot to :meth:`capture` via the ``flags`` option so events carry the exact flag values the code branched on. Prefer this over repeated ``get_feature_flag()`` calls and over ``capture(send_feature_flags=True)`` \u2014 it consolidates flag evaluation into a single ``/flags`` request per incoming request. Local evaluation is transparent: when the poller resolves a flag, the snapshot's ``$feature_flag_called`` events are tagged ``locally_evaluated=True`` and reason ``\"Evaluated locally\"``.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The user's distinct ID. If ``None``, falls back to the context distinct_id. If still unresolvable, returns an empty snapshot.", + "isOptional": false, + "type": "Number" + }, + { + "name": "groups", + "description": "Mapping of group type to group key.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "Person properties to use for evaluation.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "Group properties keyed by group type.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "If True, never fall back to remote evaluation \u2014 flags that can't be evaluated locally are simply omitted from the snapshot.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup.", + "isOptional": true, + "type": "bool" + }, + { + "name": "flag_keys", + "description": "Optional list that scopes local evaluation, the underlying ``/flags`` request, and the returned snapshot. When omitted or ``None``, all flags are evaluated. An empty list returns an empty snapshot without evaluating flags. A requested key absent from loaded local definitions is included in one remote fallback per ``evaluate_flags`` call unless ``only_evaluate_locally`` is True. If the server also does not know the key, it is omitted from the snapshot.", + "isOptional": true, + "type": "list[str]" + }, + { + "name": "device_id", + "description": "Optional device ID override. If not provided, falls back to the context device_id (which may be set via tracing headers). Used by experience-continuity flags to match users across distinct_id changes.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "FeatureFlagEvaluations" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "flags = posthog.evaluate_flags(\n \"user_123\",\n person_properties={\"plan\": \"enterprise\"},\n)\nif flags.is_enabled(\"new-dashboard\"):\n render_new_dashboard()\nposthog.capture(\"page_viewed\", distinct_id=\"user_123\", flags=flags)" + } + ] + }, + { + "id": "feature_enabled", + "title": "feature_enabled", + "description": "Check if a feature flag is enabled for a user.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "The feature flag key.", + "isOptional": true, + "type": "str" + }, + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to only evaluate locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "send_feature_flag_events", + "description": "Whether to send feature flag events.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[bool]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "is_my_flag_enabled = posthog.feature_enabled('flag-key', 'distinct_id_of_your_user')\nif is_my_flag_enabled:\n # Do something differently for this user\n # Optional: fetch the payload\n matched_flag_payload = posthog.get_feature_flag_payload('flag-key', 'distinct_id_of_your_user')" + } + ] + }, + { + "id": "feature_flag_definitions", + "title": "feature_flag_definitions", + "description": "Return feature flag definitions loaded for local evaluation. Returns: The currently loaded feature flag definitions, or ``None`` before local evaluation has loaded definitions.", + "details": "", + "category": "Feature flags", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "flush", + "title": "flush", + "description": "Attempt immediate delivery of queued events and ended spans, keeping the client reusable. For a client shared across requests or serverless invocations (including the module-level client), call ``flush()`` before a short-lived invocation returns. Use ``shutdown()`` instead only for final process cleanup or when disposing of a client that will not be reused. Flush does not stop the background workers or feature flag poller, and does not end open spans. Returning does not guarantee server receipt: pending work can remain after a timeout or span export failure, and failed events may be dropped by the consumers. Calls made directly from SDK callbacks such as ``on_error`` are deferred rather than blocking the worker that invoked the callback.", + "details": "", + "category": null, + "params": [ + { + "name": "timeout_seconds", + "description": "Seconds to wait for event queues to drain, shared across event lanes. Defaults to 10 seconds. Pass ``None`` to wait without a time limit; this does not guarantee successful delivery. Queued spans flush concurrently. The span exporter waits up to this many seconds for an in-flight flush, then uses a fresh drain budget once it acquires the lock. If the lock wait times out, the in-flight flush is left to finish. At least one span request is attempted after acquiring the lock, even with no budget left; retriable failures are retried after backoff while budget remains, with one last attempt at the deadline. Each span request is bounded by the client's ``timeout`` in seconds, and the last request is not cut short. This is therefore not a strict wall-clock limit for the whole call. With ``None``, a retriable span failure is left for a later flush rather than retried within this call.", + "isOptional": true, + "type": "float" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.capture('event_name', distinct_id='user_id')\n# Before a serverless invocation returns; keep the shared client open.\nposthog.flush(timeout_seconds=10)" + } + ] + }, + { + "id": "get_active_span", + "title": "get_active_span", + "description": "The span that is active in the current context, or ``None``. Alpha. Only entering a span (``with posthog.start_span(...) as span:``) makes it active; a span started manually is not. Use it to propagate the trace to the next service: ``span.traceparent()`` is the header value.", + "details": "", + "category": "Tracing", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[Span]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "span = posthog.get_active_span()\nif span is not None:\n headers[\"traceparent\"] = span.traceparent()" + } + ] + }, + { + "id": "get_all_flags", + "title": "get_all_flags", + "description": "Get all feature flags for a user.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to only evaluate locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "flag_keys_to_evaluate", + "description": "A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance.", + "isOptional": true, + "type": "list[str]" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[dict[str, Union[bool, str]]]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.get_all_flags('distinct_id_of_your_user')" + } + ] + }, + { + "id": "get_all_flags_and_payloads", + "title": "get_all_flags_and_payloads", + "description": "Get all feature flags and their payloads for a user.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to only evaluate locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "flag_keys_to_evaluate", + "description": "A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance.", + "isOptional": true, + "type": "list[str]" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "FlagsAndPayloads" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.get_all_flags_and_payloads('distinct_id_of_your_user')" + } + ] + }, + { + "id": "get_feature_flag", + "title": "get_feature_flag", + "description": "Get multivariate feature flag value for a user.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "The feature flag key.", + "isOptional": true, + "type": "str" + }, + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to only evaluate locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "send_feature_flag_events", + "description": "Whether to send feature flag events.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Union[bool, str, any]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "enabled_variant = posthog.get_feature_flag('flag-key', 'distinct_id_of_your_user')\nif enabled_variant == 'variant-key': # replace 'variant-key' with the key of your variant\n # Do something differently for this user\n # Optional: fetch the payload\n matched_flag_payload = posthog.get_feature_flag_payload('flag-key', 'distinct_id_of_your_user')" + } + ] + }, + { + "id": "get_feature_flag_evaluation_runtime", + "title": "get_feature_flag_evaluation_runtime", + "description": "Return where a locally loaded feature flag is meant to be evaluated.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "The feature flag key.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[FeatureFlagEvaluationRuntime]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import FeatureFlagEvaluationRuntime\n\nruntime = posthog.get_feature_flag_evaluation_runtime(\"my-flag\")\nif runtime is FeatureFlagEvaluationRuntime.SERVER:\n ..." + } + ] + }, + { + "id": "get_feature_flag_keys_by_evaluation_runtime", + "title": "get_feature_flag_keys_by_evaluation_runtime", + "description": "Return the keys of locally loaded flags that a runtime can evaluate. A flag set to ``FeatureFlagEvaluationRuntime.ALL`` suits either runtime, so it is returned for ``CLIENT`` and for ``SERVER``, and asking for ``ALL`` returns every loaded flag. Use this to decide which flags to hand to a browser when a backend serves flags to its own frontend.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "evaluation_runtime", + "description": "The runtime to match, as a ``FeatureFlagEvaluationRuntime`` or its string value.", + "isOptional": true, + "type": "FeatureFlagEvaluationRuntime" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "list[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import FeatureFlagEvaluationRuntime\n\nclient_keys = posthog.get_feature_flag_keys_by_evaluation_runtime(\n FeatureFlagEvaluationRuntime.CLIENT\n)" + } + ] + }, + { + "id": "get_feature_flag_payload", + "title": "get_feature_flag_payload", + "description": "Get the payload for a feature flag.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "The feature flag key.", + "isOptional": true, + "type": "str" + }, + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "match_value", + "description": "The specific flag value to get payload for.", + "isOptional": false, + "type": "bool" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to only evaluate locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "send_feature_flag_events", + "description": "Deprecated. Use get_feature_flag() instead if you need events.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[object]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "is_my_flag_enabled = posthog.feature_enabled('flag-key', 'distinct_id_of_your_user')\n\nif is_my_flag_enabled:\n # Do something differently for this user\n # Optional: fetch the payload\n matched_flag_payload = posthog.get_feature_flag_payload('flag-key', 'distinct_id_of_your_user')" + } + ] + }, + { + "id": "get_feature_flag_result", + "title": "get_feature_flag_result", + "description": "Get a FeatureFlagResult object which contains the flag result and payload for a key by evaluating locally or remotely depending on whether local evaluation is enabled and the flag can be locally evaluated. This also captures the `$feature_flag_called` event unless `send_feature_flag_events` is `False`.", + "details": "", + "category": null, + "params": [ + { + "name": "key", + "description": "The feature flag key.", + "isOptional": true, + "type": "str" + }, + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to only evaluate locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "send_feature_flag_events", + "description": "Whether to send feature flag events.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[FeatureFlagResult]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "flag_result = posthog.get_feature_flag_result('flag-key', 'distinct_id_of_your_user')\nif flag_result and flag_result.get_value() == 'variant-key':\n # Do something differently for this user\n # Optional: fetch the payload\n matched_flag_payload = flag_result.payload" + } + ] + }, + { + "id": "get_feature_flags_and_payloads", + "title": "get_feature_flags_and_payloads", + "description": "Get feature flags and payloads for a user.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "flag_keys_to_evaluate", + "description": "A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance.", + "isOptional": true, + "type": "list[str]" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "FlagsAndPayloads" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "result = posthog.get_feature_flags_and_payloads('')" + } + ] + }, + { + "id": "get_feature_payloads", + "title": "get_feature_payloads", + "description": "Get feature flag payloads for a user, preserving valid serialized JSON.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "flag_keys_to_evaluate", + "description": "A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance.", + "isOptional": true, + "type": "list[str]" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "dict[str, Optional[str]]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "payloads = posthog.get_feature_payloads('')" + } + ] + }, + { + "id": "get_feature_variants", + "title": "get_feature_variants", + "description": "Get feature flag variants for a user.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "flag_keys_to_evaluate", + "description": "A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance.", + "isOptional": true, + "type": "list[str]" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "dict[str, Union[bool, str]]" + } + }, + { + "id": "get_flags_decision", + "title": "get_flags_decision", + "description": "Get feature flags decision.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": false, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "flag_keys_to_evaluate", + "description": "A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance.", + "isOptional": true, + "type": "list[str]" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "FlagsResponse" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "decision = posthog.get_flags_decision('user123')" + } + ] + }, + { + "id": "get_remote_config_payload", + "title": "get_remote_config_payload", + "description": "Get the payload for a remote config feature flag.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "The remote config feature flag key.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "get_tags", + "title": "get_tags", + "description": "Get all tags from the current context. Returns: Dict of all tags in the current context.", + "details": "", + "category": "Contexts", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "dict[str, Any]" + } + }, + { + "id": "group_identify", + "title": "group_identify", + "description": "Identify a group and set its properties.", + "details": "", + "category": "Identification", + "params": [ + { + "name": "group_type", + "description": "The type of group (e.g., 'company', 'team'). Required - the call is dropped with a warning if it is missing or empty.", + "isOptional": true, + "type": "str" + }, + { + "name": "group_key", + "description": "The unique identifier for the group. Required - the call is dropped with a warning if it is missing or empty.", + "isOptional": true, + "type": "str" + }, + { + "name": "properties", + "description": "A dictionary of properties to set on the group.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "timestamp", + "description": "The timestamp of the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC.", + "isOptional": false, + "type": "datetime" + }, + { + "name": "uuid", + "description": "A unique identifier for the event. If provided, it must be a valid UUID string or uuid.UUID instance; invalid values are ignored and replaced with a newly generated UUID.", + "isOptional": false, + "type": "str" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this event.", + "isOptional": true, + "type": "bool" + }, + { + "name": "distinct_id", + "description": "The distinct ID of the user performing the action.", + "isOptional": false, + "type": "Number" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.group_identify('company', 'company_id_in_your_db', {\n 'name': 'Awesome Inc.',\n 'employees': 11\n})" + } + ] + }, + { + "id": "identify_context", + "title": "identify_context", + "description": "Identify the current context with a distinct ID.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "distinct_id", + "description": "The distinct ID to associate with the current context and its children.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + } + }, + { + "id": "join", + "title": "join", + "description": "Attempt to process queued events and end the consumer threads. Do not use directly, call `shutdown()` instead. Failed or undrainable events may be dropped and reported through logging or ``on_error``; returning does not guarantee server receipt. Lifecycle cleanup is attempted once, and cleanup failures are logged without retry.", + "details": "", + "category": null, + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.join()" + } + ] + }, + { + "id": "load_feature_flags", + "title": "load_feature_flags", + "description": "Load feature flags for local evaluation.", + "details": "", + "category": "Feature flags", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.load_feature_flags()" + } + ] + }, + { + "id": "new_context", + "title": "new_context", + "description": "Create a new context for managing shared state. Learn more about [contexts](/docs/libraries/python#contexts).", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "fresh", + "description": "Whether to create a fresh context that doesn't inherit from parent.", + "isOptional": false, + "type": "bool" + }, + { + "name": "capture_exceptions", + "description": "Whether to automatically capture exceptions in this context. If omitted, defaults to this client's exception autocapture setting.", + "isOptional": true, + "type": "bool" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "with client.new_context():\n client.identify_context('')\n client.capture('event_name')" + } + ] + }, + { + "id": "scoped", + "title": "scoped", + "description": "Decorator that creates a new context for the wrapped function using this client.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "fresh", + "description": "Whether to create a fresh context that doesn't inherit from parent.", + "isOptional": false, + "type": "bool" + }, + { + "name": "capture_exceptions", + "description": "Whether to automatically capture exceptions in this context. If omitted, defaults to this client's exception autocapture setting.", + "isOptional": true, + "type": "bool" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "set", + "title": "set", + "description": "Set properties on a person profile.", + "details": "", + "category": "Identification", + "params": [ + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalSetArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Set with distinct id", + "code": "# Set with distinct id\nposthog.set(distinct_id='user123', properties={'name': 'Max Hedgehog'})" + } + ] + }, + { + "id": "set_context_device_id", + "title": "set_context_device_id", + "description": "Set the device ID for the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "device_id", + "description": "The device ID to associate with the current context and its children.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + } + }, + { + "id": "set_context_session", + "title": "set_context_session", + "description": "Set the session ID for the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "session_id", + "description": "The session ID to associate with the current context and its children.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + } + }, + { + "id": "set_once", + "title": "set_once", + "description": "Set properties on a person profile only if they haven't been set before.", + "details": "", + "category": "Identification", + "params": [ + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalSetArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.set_once(distinct_id='user123', properties={'initial_signup_date': '2024-01-01'})" + } + ] + }, + { + "id": "shutdown", + "title": "shutdown", + "description": "Attempt final delivery and permanently shut down the client. Call this for final process cleanup or when disposing of a client that will not be reused. It closes capture lanes, stops background workers and the feature flag poller, and tears down integrations. Do not call it at the end of each request or serverless invocation when the client is shared; call ``flush()`` before returning instead. A shut-down client cannot be reused; create a new client if needed. Normally this method blocks until queued events have been attempted and cleanup finishes. Failed or undrainable events may be dropped and reported through logging or ``on_error``; returning does not guarantee server receipt. Event draining has no timeout. Queued spans get one final flush with up to 30 seconds waiting for an in-flight flush, then a fresh 30-second drain budget; the last HTTP request is not cut short. Any spans it cannot send are discarded with a warning, as are spans still open. Lifecycle cleanup is attempted once, and cleanup failures are logged without retry. When called directly from an SDK callback such as ``on_error``, shutdown is deferred to avoid blocking the worker that invoked the callback. If the callback must coordinate a blocking shutdown, have it signal an application-owned thread and return before that thread calls shutdown. Do not wait inside the callback for another thread or task that calls a lifecycle method.", + "details": "", + "category": null, + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.shutdown()" + } + ] + }, + { + "id": "start_span", + "title": "start_span", + "description": "Start a span for distributed tracing. Alpha. Returns a span handle. Use it as a context manager to make it the active span for the block and end it on exit (recording a raised exception on the way out); or call ``end()`` yourself for a span that cannot wrap a block. Spans started inside the block nest under it automatically. Always returns a usable handle, even when tracing is off, so calling code never branches.", + "details": "", + "category": "Tracing", + "params": [ + { + "name": "name", + "description": "A low-cardinality operation name, e.g. ``GET /users/:id``. Variable values belong in attributes, not the name.", + "isOptional": true, + "type": "str" + }, + { + "name": "kind", + "description": "``internal`` (default), ``server``, ``client``, ``producer`` or ``consumer``.", + "isOptional": true, + "type": "str" + }, + { + "name": "attributes", + "description": "Initial attributes.", + "isOptional": true, + "type": "Mapping[str, Any]" + }, + { + "name": "parent", + "description": "A span handle, or an inbound W3C ``traceparent`` header value to continue a remote trace. Defaults to the active span. A forked child starts with no active span; pass the parent span to continue a trace across a fork.", + "isOptional": false, + "type": "Span" + }, + { + "name": "tracestate", + "description": "The inbound ``tracestate`` header accompanying a ``traceparent`` string ``parent``; preserved and propagated.", + "isOptional": true, + "type": "str" + }, + { + "name": "start_time", + "description": "A ``datetime`` or epoch seconds, to backdate the span.", + "isOptional": false, + "type": "datetime" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Span" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog = Posthog(\"\", traces={\"service_name\": \"checkout-api\"})\n\nwith posthog.start_span(\"POST /checkout\", parent=request.headers.get(\"traceparent\")) as span:\n span.set_attribute(\"plan\", user.plan)\n with posthog.start_span(\"db.query\", kind=\"client\"):\n ...\n outgoing_headers = {\"traceparent\": span.traceparent()}" + } + ] + }, + { + "id": "tag", + "title": "tag", + "description": "Add a tag to the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "name", + "description": "The tag key.", + "isOptional": true, + "type": "str" + }, + { + "name": "value", + "description": "The tag value.", + "isOptional": true, + "type": "Any" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + } + } + ] + }, + { + "id": "PostHogModule", + "title": "PostHog Module Functions", + "description": "Global functions available in the PostHog module", + "functions": [ + { + "id": "alias", + "title": "alias", + "description": "Associate user behaviour before and after they e.g. register, login, or perform some other identifying action.", + "details": "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?\". Particularly useful for associating user behaviour before and after they e.g. register, login, or perform some other identifying action.", + "category": "Identification", + "params": [ + { + "name": "previous_id", + "description": "The unique ID of the user before", + "isOptional": true, + "type": "Number" + }, + { + "name": "distinct_id", + "description": "The current unique id", + "isOptional": true, + "type": "str" + }, + { + "name": "timestamp", + "description": "Optional timestamp for the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC.", + "isOptional": false, + "type": "datetime" + }, + { + "name": "uuid", + "description": "Optional UUID for the event", + "isOptional": true, + "type": "str" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup", + "isOptional": true, + "type": "bool" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Alias user", + "code": "# Alias user\nfrom posthog import alias\nalias(previous_id='distinct_id', distinct_id='alias_id')" + } + ] + }, + { + "id": "capture", + "title": "capture", + "description": "Capture anything a user does within your system.", + "details": "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. A capture call requires an event name to specify the event. We recommend using [verb] [noun], like `movie played` or `movie updated` to easily identify what your events mean later on. Capture takes a number of optional arguments, which are defined by the `OptionalCaptureArgs` type.", + "category": "Events", + "params": [ + { + "name": "event", + "description": "The event name to specify the event **kwargs: Optional arguments including:", + "isOptional": true, + "type": "str" + }, + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalCaptureArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Context and capture usage", + "code": "# Context and capture usage\nfrom posthog import new_context, identify_context, tag_context, capture\n# Enter a new context (e.g. a request/response cycle, an instance of a background job, etc)\nwith new_context():\n # Associate this context with some user, by distinct_id\n identify_context('some user')\n\n # Capture an event, associated with the context-level distinct ID ('some user')\n capture('movie started')\n\n # Capture an event associated with some other user (overriding the context-level distinct ID)\n capture('movie joined', distinct_id='some-other-user')\n\n # Capture an event with some properties\n capture('movie played', properties={'movie_id': '123', 'category': 'romcom'})\n\n # Capture an event with some properties\n capture('purchase', properties={'product_id': '123', 'category': 'romcom'})\n # Capture an event with some associated group\n capture('purchase', groups={'company': 'id:5'})\n\n # Adding a tag to the current context will cause it to appear on all subsequent events\n tag_context('some-tag', 'some-value')\n\n capture('another-event') # Will be captured with `'some-tag': 'some-value'` in the properties dict" + }, + { + "id": "example_2", + "name": "Set event properties", + "code": "# Set event properties\nfrom posthog import capture\ncapture(\n \"user_signed_up\",\n distinct_id=\"distinct_id_of_the_user\",\n properties={\n \"login_type\": \"email\",\n \"is_free_trial\": \"true\"\n }\n)" + } + ] + }, + { + "id": "capture_ai", + "title": "capture_ai", + "description": "Capture an AI event on the dedicated AI capture endpoint. Beta: the signature is stable; operational limits (per-event size cap, batching, endpoint) may change without notice. Takes the same arguments and returns the same value as `capture()`: the event UUID, or None when the event was not admitted (disabled client, or dropped by `before_send`). The event is delivered on an isolated queue with its own consumer pool and a higher per-event size cap, posting to the dedicated AI ingestion endpoint. The payload is sent as given \u2014 no redaction or truncation is applied here.", + "details": "", + "category": "Events", + "params": [ + { + "name": "event", + "description": "The event name, normally one of the `$ai_*` event names. **kwargs: Same optional arguments as `capture()`.", + "isOptional": true, + "type": "str" + }, + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalCaptureArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import capture_ai\n\nuuid = capture_ai(\n \"$ai_generation\",\n distinct_id=\"user_123\",\n properties={\"$ai_model\": \"gpt-5\"},\n)" + } + ] + }, + { + "id": "capture_exception", + "title": "capture_exception", + "description": "Capture exceptions that happen in your code.", + "details": "Capture exception is idempotent - if it is called twice with the same exception instance, only a occurrence will be tracked in posthog. This is because, generally, contexts will cause exceptions to be captured automatically. However, to ensure you track an exception, if you catch and do not re-raise it, capturing it manually is recommended, unless you are certain it will have crossed a context boundary (e.g. by existing a `with posthog.new_context():` block already). If the passed exception was raised and caught, the captured stack trace will consist of every frame between where the exception was raised and the point at which it is captured (the \"traceback\"). If the passed exception was never raised, e.g. if you call `posthog.capture_exception(ValueError(\"Some Error\"))`, the stack trace captured will be the full stack trace at the moment the exception was captured. Note that heavy use of contexts will lead to truncated stack traces, as the exception will be captured by the context entered most recently, 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. `capture_exception` takes the same set of optional arguments as `capture`.", + "category": "Events", + "params": [ + { + "name": "exception", + "description": "The exception to capture. If not provided, the current exception is captured via `sys.exc_info()` **kwargs: Optional capture arguments including distinct_id, properties, timestamp, uuid, groups, flags, send_feature_flags, and disable_geoip. Overriding reserved exception properties through ``properties`` is deprecated and will stop working in the next major version.", + "isOptional": false, + "type": "BaseException" + }, + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalCaptureArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Capture exception", + "code": "# Capture exception\nfrom posthog import capture_exception\ntry:\n risky_operation()\nexcept Exception as e:\n capture_exception(e)" + } + ] + }, + { + "id": "evaluate_flags", + "title": "evaluate_flags", + "description": "Evaluate all feature flags for a user in a single call and return a :class:`FeatureFlagEvaluations` snapshot. Branch on ``.is_enabled()`` / ``.get_flag()`` and pass the same snapshot to ``capture()`` via the ``flags`` option so events carry the exact flag values the code branched on. Prefer this over repeated ``get_feature_flag()`` calls and over ``capture(send_feature_flags=True)`` \u2014 it consolidates flag evaluation into a single ``/flags`` request per incoming request.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The user's distinct ID. If ``None``, falls back to the context distinct_id. If still unresolvable, returns an empty snapshot.", + "isOptional": false, + "type": "Number" + }, + { + "name": "groups", + "description": "Mapping of group type to group key.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "Person properties to use for evaluation.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "Group properties keyed by group type.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "If ``True``, never fall back to remote evaluation and omit flags that cannot be evaluated locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup.", + "isOptional": true, + "type": "bool" + }, + { + "name": "flag_keys", + "description": "Optional list that scopes local evaluation, the underlying ``/flags`` request, and the returned snapshot. When omitted or ``None``, all flags are evaluated. An empty list returns an empty snapshot without evaluating flags. A requested key absent from loaded local definitions is included in one remote fallback per ``evaluate_flags`` call unless ``only_evaluate_locally`` is ``True``. If the server also does not know the key, it is omitted from the snapshot.", + "isOptional": true, + "type": "list[str]" + }, + { + "name": "device_id", + "description": "Optional device ID override. If not provided, falls back to the context device_id (which may be set via tracing headers). Used by experience-continuity flags to match users across distinct_id changes.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "FeatureFlagEvaluations" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import evaluate_flags, capture\nflags = evaluate_flags(\"user_123\", person_properties={\"plan\": \"enterprise\"})\nif flags.is_enabled(\"new-dashboard\"):\n render_new_dashboard()\ncapture(\"page_viewed\", distinct_id=\"user_123\", flags=flags)" + } + ] + }, + { + "id": "feature_enabled", + "title": "feature_enabled", + "description": "Use feature flags to enable or disable features for users.", + "details": "You can call `posthog.load_feature_flags()` before to make sure you're not doing unexpected requests.", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "The feature flag key", + "isOptional": true, + "type": "str" + }, + { + "name": "distinct_id", + "description": "The user's distinct ID", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "Groups mapping", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "Person properties", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "Group properties", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to evaluate only locally", + "isOptional": false, + "type": "bool" + }, + { + "name": "send_feature_flag_events", + "description": "Whether to send feature flag events", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "Optional device ID override for experience-continuity flags", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[bool]" + }, + "examples": [ + { + "id": "example_1", + "name": "Boolean feature flag", + "code": "# Boolean feature flag\nfrom posthog import feature_enabled, get_feature_flag_payload\nis_my_flag_enabled = feature_enabled('flag-key', 'distinct_id_of_your_user')\nif is_my_flag_enabled:\n matched_flag_payload = get_feature_flag_payload('flag-key', 'distinct_id_of_your_user')" + } + ] + }, + { + "id": "feature_flag_definitions", + "title": "feature_flag_definitions", + "description": "Returns loaded feature flags.", + "details": "Returns loaded feature flags, if any. Helpful for debugging what flag information you have loaded.", + "category": "Feature flags", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import feature_flag_definitions\ndefinitions = feature_flag_definitions()" + } + ] + }, + { + "id": "flush", + "title": "flush", + "description": "Attempt immediate delivery of queued events and ended spans, keeping the global client reusable. Call ``flush()`` before a short-lived request or serverless invocation returns when using the shared module-level client. Use ``shutdown()`` only for final process cleanup or a client that will not be reused. Flush does not stop the background workers or feature flag poller, and does not end open spans. Returning does not guarantee server receipt: pending work can remain after a timeout or span export failure, and failed events may be dropped by consumers. Calls made directly from SDK callbacks such as ``on_error`` are deferred rather than blocking the worker that invoked the callback.", + "details": "", + "category": "Client management", + "params": [ + { + "name": "timeout_seconds", + "description": "Seconds to wait for event queues to drain, shared across event lanes. Defaults to 10 seconds. Pass ``None`` to wait without a time limit; this does not guarantee successful delivery. Queued spans flush concurrently, waiting up to this many seconds for an in-flight flush before starting a fresh drain budget. A lock wait timeout leaves the in-flight flush to finish. At least one span request is attempted after acquiring the lock, even with no budget left; retriable failures are retried after backoff while budget remains, with one last attempt at the deadline. The last span request is not cut short and is bounded by the client's ``timeout`` in seconds, so this is not a strict wall-clock limit for the whole call. With ``None``, a retriable span failure is left for a later flush rather than retried within this call.", + "isOptional": true, + "type": "float" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "import posthog\nposthog.capture('event_name', distinct_id='user_id')\n# Before a serverless invocation returns; keep the global client open.\nposthog.flush(timeout_seconds=10)" + } + ] + }, + { + "id": "get_active_span", + "title": "get_active_span", + "description": "The span that is active in the current context, or ``None``. Alpha. Only entering a span (``with posthog.start_span(...) as span:``) makes it active; a span started manually is not. Use it to propagate the trace to the next service: ``span.traceparent()`` is the header value.", + "details": "", + "category": "Tracing", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[Span]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "span = posthog.get_active_span()\nif span is not None:\n headers[\"traceparent\"] = span.traceparent()" + } + ] + }, + { + "id": "get_all_flags", + "title": "get_all_flags", + "description": "Get all flags for a given user.", + "details": "Flags are key-value pairs where the key is the flag key and the value is the flag variant, or True, or False.", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The user's distinct ID", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "Groups mapping", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "Person properties", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "Group properties", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to evaluate only locally", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "Optional device ID override for experience-continuity flags", + "isOptional": true, + "type": "str" + }, + { + "name": "flag_keys_to_evaluate", + "description": "Optional list of flag keys to evaluate (evaluates all if None)", + "isOptional": true, + "type": "list[str]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[dict[str, Union[bool, str]]]" + }, + "examples": [ + { + "id": "example_1", + "name": "All flags for user", + "code": "# All flags for user\nfrom posthog import get_all_flags\nget_all_flags('distinct_id_of_your_user')" + } + ] + }, + { + "id": "get_all_flags_and_payloads", + "title": "get_all_flags_and_payloads", + "description": "Get all feature flag values and payloads for a user.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The user's distinct ID.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "Mapping of group type to group key.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "Person properties to use for evaluation.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "Group properties keyed by group type.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to evaluate only locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup.", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "Optional device ID override for experience-continuity flags.", + "isOptional": true, + "type": "str" + }, + { + "name": "flag_keys_to_evaluate", + "description": "Optional list of flag keys to evaluate. Evaluates all flags when omitted.", + "isOptional": true, + "type": "list[str]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "FlagsAndPayloads" + } + }, + { + "id": "get_feature_flag", + "title": "get_feature_flag", + "description": "Get feature flag variant for users. Used with experiments.", + "details": "`groups` are a mapping from group type to group key. So, if you have a group type of \"organization\" and a group key of \"5\", you would pass groups={\"organization\": \"5\"}. `group_properties` take the format: { group_type_name: { group_properties } }. So, for example, if you have the group type \"organization\" and the group key \"5\", with the properties name, and employee count, you'll send these as: group_properties={\"organization\": {\"name\": \"PostHog\", \"employees\": 11}}.", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "The feature flag key", + "isOptional": true, + "type": "str" + }, + { + "name": "distinct_id", + "description": "The user's distinct ID", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "Groups mapping from group type to group key", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "Person properties", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "Group properties in format { group_type_name: { group_properties } }", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to evaluate only locally", + "isOptional": false, + "type": "bool" + }, + { + "name": "send_feature_flag_events", + "description": "Whether to send feature flag events", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "Optional device ID override for experience-continuity flags", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Union[bool, str, any]" + }, + "examples": [ + { + "id": "example_1", + "name": "Multivariate feature flag", + "code": "# Multivariate feature flag\nfrom posthog import get_feature_flag, get_feature_flag_payload\nenabled_variant = get_feature_flag('flag-key', 'distinct_id_of_your_user')\nif enabled_variant == 'variant-key':\n matched_flag_payload = get_feature_flag_payload('flag-key', 'distinct_id_of_your_user')" + } + ] + }, + { + "id": "get_feature_flag_evaluation_runtime", + "title": "get_feature_flag_evaluation_runtime", + "description": "Return where a locally loaded feature flag is meant to be evaluated.", + "details": "Reads the `evaluation_runtime` each flag definition carries, so no extra request is made. Returns `None` when local evaluation has not loaded a definition for this key. A definition that carries no runtime reports `FeatureFlagEvaluationRuntime.ALL`, the default PostHog applies.", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[FeatureFlagEvaluationRuntime]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import FeatureFlagEvaluationRuntime, get_feature_flag_evaluation_runtime\nruntime = get_feature_flag_evaluation_runtime(\"my-flag\")" + } + ] + }, + { + "id": "get_feature_flag_keys_by_evaluation_runtime", + "title": "get_feature_flag_keys_by_evaluation_runtime", + "description": "Return the keys of locally loaded flags that a runtime can evaluate.", + "details": "A flag set to `FeatureFlagEvaluationRuntime.ALL` suits either runtime, so it is returned for `CLIENT` and for `SERVER`. Use this to decide which flags to hand to a browser when a backend serves flags to its own frontend.", + "category": "Feature flags", + "params": [ + { + "name": "evaluation_runtime", + "description": "", + "isOptional": true, + "type": "FeatureFlagEvaluationRuntime" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "list[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import FeatureFlagEvaluationRuntime, get_feature_flag_keys_by_evaluation_runtime\nclient_keys = get_feature_flag_keys_by_evaluation_runtime(FeatureFlagEvaluationRuntime.CLIENT)" + } + ] + }, + { + "id": "get_feature_flag_payload", + "title": "get_feature_flag_payload", + "description": "Get the payload associated with a feature flag value. Deprecated for new code. Prefer ``evaluate_flags()`` and ``flags.get_flag_payload(key)`` so flag evaluation happens once per request.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "The feature flag key.", + "isOptional": true, + "type": "str" + }, + { + "name": "distinct_id", + "description": "The user's distinct ID.", + "isOptional": true, + "type": "Number" + }, + { + "name": "match_value", + "description": "Optional flag value to use when selecting a payload.", + "isOptional": false, + "type": "bool" + }, + { + "name": "groups", + "description": "Mapping of group type to group key.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "Person properties to use for evaluation.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "Group properties keyed by group type.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to evaluate only locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "send_feature_flag_events", + "description": "Whether to send a $feature_flag_called event.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup.", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "Optional device ID override for experience-continuity flags.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[object]" + } + }, + { + "id": "get_feature_flag_result", + "title": "get_feature_flag_result", + "description": "Get a FeatureFlagResult object which contains the flag result and payload. This method evaluates a feature flag and returns a FeatureFlagResult object containing: - enabled: Whether the flag is enabled - variant: The variant value if the flag has variants - payload: The payload associated with the flag (automatically deserialized from JSON) - key: The flag key - reason: Why the flag was enabled/disabled", + "details": "", + "category": null, + "params": [ + { + "name": "key", + "description": "The feature flag key.", + "isOptional": true, + "type": "str" + }, + { + "name": "distinct_id", + "description": "The user's distinct ID.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "Mapping of group type to group key.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "Person properties to use for evaluation.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "Group properties keyed by group type.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to evaluate only locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "send_feature_flag_events", + "description": "Whether to send a $feature_flag_called event.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup.", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "Optional device ID override for experience-continuity flags.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[FeatureFlagResult]" + } + }, + { + "id": "get_remote_config_payload", + "title": "get_remote_config_payload", + "description": "Get the payload for a remote config feature flag.", + "details": "", + "category": null, + "params": [ + { + "name": "key", + "description": "The key of the feature flag", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "get_tags", + "title": "get_tags", + "description": "Get all tags from the current context. Returns: Dict of all tags in the current context", + "details": "", + "category": "Contexts", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "dict[str, Any]" + } + }, + { + "id": "group_identify", + "title": "group_identify", + "description": "Set properties on a group.", + "details": "", + "category": "Identification", + "params": [ + { + "name": "group_type", + "description": "Type of your group. Required - the call is dropped with a warning if it is missing or empty.", + "isOptional": true, + "type": "str" + }, + { + "name": "group_key", + "description": "Unique identifier of the group. Required - the call is dropped with a warning if it is missing or empty.", + "isOptional": true, + "type": "str" + }, + { + "name": "properties", + "description": "Properties to set on the group", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "timestamp", + "description": "Optional timestamp for the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC.", + "isOptional": false, + "type": "datetime" + }, + { + "name": "uuid", + "description": "Optional UUID for the event", + "isOptional": true, + "type": "str" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup", + "isOptional": true, + "type": "bool" + }, + { + "name": "distinct_id", + "description": "Optional distinct ID of the user performing the action", + "isOptional": false, + "type": "Number" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Group identify", + "code": "# Group identify\nfrom posthog import group_identify\ngroup_identify('company', 'company_id_in_your_db', {\n 'name': 'Awesome Inc.',\n 'employees': 11\n})" + } + ] + }, + { + "id": "identify_context", + "title": "identify_context", + "description": "Identify the current context with a distinct ID.", + "details": "", + "category": "Identification", + "params": [ + { + "name": "distinct_id", + "description": "The distinct ID to associate with the current context and its children", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import identify_context\nidentify_context(\"user_123\")" + } + ] + }, + { + "id": "join", + "title": "join", + "description": "Attempt to process queued events and stop the client's background workers. Use `shutdown()` directly in most cases. Failed or undrainable events may be dropped and reported through logging or ``on_error``; returning does not guarantee server receipt. Lifecycle cleanup is attempted once, and cleanup failures are logged without retry.", + "details": "", + "category": "Client management", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import join\njoin()" + } + ] + }, + { + "id": "load_feature_flags", + "title": "load_feature_flags", + "description": "Load feature flag definitions from PostHog.", + "details": "", + "category": "Feature flags", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import load_feature_flags\nload_feature_flags()" + } + ] + }, + { + "id": "new_context", + "title": "new_context", + "description": "Create a new context scope that will be active for the duration of the with block.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "fresh", + "description": "Whether to start with a fresh context (default: False)", + "isOptional": false, + "type": "bool" + }, + { + "name": "capture_exceptions", + "description": "Whether to capture exceptions raised within the context. If omitted, defaults to the relevant client's exception autocapture setting.", + "isOptional": true, + "type": "bool" + }, + { + "name": "client", + "description": "Optional Posthog client instance to use for this context (default: None)", + "isOptional": true, + "type": "Client" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import new_context, tag, capture\nwith new_context():\n tag(\"request_id\", \"123\")\n capture(\"event_name\", properties={\"property\": \"value\"})" + } + ] + }, + { + "id": "scoped", + "title": "scoped", + "description": "Decorator that creates a new context for the function.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "fresh", + "description": "Whether to start with a fresh context (default: False)", + "isOptional": false, + "type": "bool" + }, + { + "name": "capture_exceptions", + "description": "Whether to capture and track exceptions with posthog error tracking. If omitted, defaults to the global exception autocapture setting.", + "isOptional": true, + "type": "bool" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import scoped, tag, capture\n@scoped()\ndef process_payment(payment_id):\n tag(\"payment_id\", payment_id)\n capture(\"payment_started\")" + } + ] + }, + { + "id": "set", + "title": "set", + "description": "Set properties on a user record.", + "details": "This will overwrite previous people property values. Generally operates similar to `capture`, with distinct_id being an optional argument, defaulting to the current context's distinct ID. If there is no context-level distinct ID, and no override distinct_id is passed, this function will do nothing. Context tags are folded into $set properties, so tagging the current context and then calling `set` will cause those tags to be set on the user (unlike capture, which causes them to just be set on the event).", + "category": "Identification", + "params": [ + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalSetArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Set person properties", + "code": "# Set person properties\nfrom posthog import set\nset(distinct_id='distinct_id', properties={'name': 'Max Hedgehog'})" + } + ] + }, + { + "id": "set_capture_exception_code_variables_context", + "title": "set_capture_exception_code_variables_context", + "description": "Override code-variable capture for exceptions in the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "enabled", + "description": "Whether exceptions captured in this context should include local variable values from stack frames.", + "isOptional": true, + "type": "bool" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "set_code_variables_detect_secrets_context", + "title": "set_code_variables_detect_secrets_context", + "description": "Whether to apply entropy-based secret detection as a last-resort redaction of high-entropy values (API keys, tokens, strong passwords) in captured code variables for the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "enabled", + "description": "", + "isOptional": true, + "type": "bool" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "set_code_variables_ignore_patterns_context", + "title": "set_code_variables_ignore_patterns_context", + "description": "Override code-variable ignore patterns for exceptions in the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "ignore_patterns", + "description": "Variable-name patterns that should be omitted entirely when code variables are captured.", + "isOptional": true, + "type": "list[str]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "set_code_variables_mask_patterns_context", + "title": "set_code_variables_mask_patterns_context", + "description": "Override code-variable mask patterns for exceptions in the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "mask_patterns", + "description": "Variable-name patterns whose values should be replaced with ``***`` when code variables are captured.", + "isOptional": true, + "type": "list[str]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "set_code_variables_mask_url_credentials_context", + "title": "set_code_variables_mask_url_credentials_context", + "description": "Whether to scrub credentials embedded in URLs/DSNs (e.g. user:pass@host) from captured code variables for the current context.", + "details": "", + "category": null, + "params": [ + { + "name": "enabled", + "description": "", + "isOptional": true, + "type": "bool" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "set_context_device_id", + "title": "set_context_device_id", + "description": "Set the device ID for the current context, associating all feature flag requests in this or child contexts with the given device ID.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "device_id", + "description": "The device ID to associate with the current context and its children", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import set_context_device_id\nset_context_device_id(\"device_123\")" + } + ] + }, + { + "id": "set_context_session", + "title": "set_context_session", + "description": "Set the session ID for the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "session_id", + "description": "The session ID to associate with the current context and its children", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import set_context_session\nset_context_session(\"session_123\")" + } + ] + }, + { + "id": "set_once", + "title": "set_once", + "description": "Set properties on a user record, only if they do not yet exist.", + "details": "This will not overwrite previous people property values, unlike `set`. Otherwise, operates in an identical manner to `set`.", + "category": "Identification", + "params": [ + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalSetArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Set property once", + "code": "# Set property once\nfrom posthog import set_once\nset_once(distinct_id='distinct_id', properties={'initial_url': '/blog'})" + } + ] + }, + { + "id": "setup", + "title": "setup", + "description": "Create or return the global PostHog client configured by module settings. Most applications should either instantiate ``Posthog`` directly or set ``posthog.api_key``/other module settings before calling top-level helpers. ``setup()`` is called automatically by global APIs such as ``capture()``. Returns: The global ``Client`` instance. If both ``api_key`` and ``project_api_key`` are missing or blank, the client is disabled and module-level calls become no-ops.", + "details": "", + "category": "Initialization", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Client" + } + }, + { + "id": "shutdown", + "title": "shutdown", + "description": "Attempt final delivery and permanently shut down the global client. Call this for final process cleanup, not at the end of each request or serverless invocation that shares the module-level client. Use ``flush()`` before returning from those invocations instead. Shutdown closes capture lanes, stops background workers and the feature flag poller, and tears down integrations. The global client is not recreated automatically after shutdown. This normally blocks until queued events have been attempted and cleanup finishes. Failed or undrainable events may be dropped and reported through logging or ``on_error``; returning does not guarantee server receipt. Event draining has no timeout. Queued spans get a final flush with up to 30 seconds waiting for an in-flight flush, then a fresh 30-second drain budget; the last HTTP request is not cut short. Unsent queued spans and still-open spans are discarded with a warning. Lifecycle cleanup is attempted once, and cleanup failures are logged without retry. Calls made directly from SDK callbacks such as ``on_error`` are deferred to avoid deadlocking the worker. If blocking completion is required, signal an application-owned thread, return from the callback, and call ``shutdown()`` from that thread. Do not wait inside a callback for another thread or task calling a lifecycle method.", + "details": "", + "category": "Client management", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import shutdown\nshutdown()" + } + ] + }, + { + "id": "start_span", + "title": "start_span", + "description": "Start a span for distributed tracing. Alpha. Returns a span handle. Use it as a context manager to make it the active span for the block and end it on exit (recording a raised exception on the way out); or call ``end()`` yourself for a span that cannot wrap a block. Spans started inside the block nest under it automatically. Always returns a usable handle, even when tracing is off, so calling code never branches.", + "details": "", + "category": "Tracing", + "params": [ + { + "name": "name", + "description": "A low-cardinality operation name, e.g. ``GET /users/:id``. Variable values belong in attributes, not the name.", + "isOptional": true, + "type": "str" + }, + { + "name": "kind", + "description": "``internal`` (default), ``server``, ``client``, ``producer`` or ``consumer``.", + "isOptional": true, + "type": "str" + }, + { + "name": "attributes", + "description": "Initial attributes.", + "isOptional": true, + "type": "Mapping[str, Any]" + }, + { + "name": "parent", + "description": "A span handle, or an inbound W3C ``traceparent`` header value to continue a remote trace. Defaults to the active span.", + "isOptional": false, + "type": "Span" + }, + { + "name": "tracestate", + "description": "The inbound ``tracestate`` header accompanying a ``traceparent`` string ``parent``; preserved and propagated.", + "isOptional": true, + "type": "str" + }, + { + "name": "start_time", + "description": "A ``datetime`` or epoch seconds, to backdate the span.", + "isOptional": false, + "type": "datetime" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Span" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "import posthog\nposthog.traces = {\"service_name\": \"checkout-api\"}\n\nwith posthog.start_span(\"POST /checkout\", parent=request.headers.get(\"traceparent\")) as span:\n span.set_attribute(\"plan\", user.plan)\n with posthog.start_span(\"db.query\", kind=\"client\"):\n ...\n outgoing_headers = {\"traceparent\": span.traceparent()}" + } + ] + }, + { + "id": "tag", + "title": "tag", + "description": "Add a tag to the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "name", + "description": "The tag key", + "isOptional": true, + "type": "str" + }, + { + "name": "value", + "description": "The tag value", + "isOptional": true, + "type": "Any" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import tag\ntag(\"user_id\", \"123\")" + } + ] + } + ] + } + ], + "categories": [ + "Initialization", + "Identification", + "Capture", + "Error Tracking", + "Feature flags", + "Tracing", + "Contexts", + "Events", + "Client management" + ] +} \ No newline at end of file diff --git a/references/posthog-python-references-latest.json b/references/posthog-python-references-latest.json index 00e8b9d0..9df82cc5 100644 --- a/references/posthog-python-references-latest.json +++ b/references/posthog-python-references-latest.json @@ -2,7 +2,7 @@ "id": "posthog-python", "hogRef": "0.3", "info": { - "version": "7.75.11", + "version": "7.76.0", "id": "posthog-python", "title": "PostHog Python SDK", "description": "Integrate PostHog into any python application.", From e5e1b47db5a2899977bb50563b561e163f89a9cf Mon Sep 17 00:00:00 2001 From: alexcastgonz <82875882+pcbeingused333@users.noreply.github.com> Date: Wed, 28 Oct 2026 04:40:27 -0400 Subject: [PATCH 25/60] feat(ai): record the Anthropic web search count in the LangChain callback handler (#1097) * feat(ai): record the Anthropic web search count in the LangChain callback handler Co-Authored-By: Claude Sonnet 5.5 * chore: update public API snapshot for ModelUsage.web_search_count Co-Authored-By: Claude Opus 5.5 * chore: regenerate LangChain ModelUsage API snapshot * fix(ai): read the LangChain web search count from llm_output usage and omit zero Co-Authored-By: Claude Sonnet 5.5 --------- Co-authored-by: Claude Sonnet 5.5 Co-authored-by: Manoel Aranda Neto (cherry picked from commit b0a2b59dc09e96903cdf97ea3c8ab5298bdff7bb) --- .../changesets/langchain-web-search-count.md | 5 ++ posthog/ai/langchain/callbacks.py | 35 +++++++++++++ posthog/test/ai/langchain/test_callbacks.py | 51 +++++++++++++++++++ references/public_api_snapshot.txt | 3 +- 4 files changed, 93 insertions(+), 1 deletion(-) create mode 100644 .sampo/changesets/langchain-web-search-count.md diff --git a/.sampo/changesets/langchain-web-search-count.md b/.sampo/changesets/langchain-web-search-count.md new file mode 100644 index 00000000..465d4d91 --- /dev/null +++ b/.sampo/changesets/langchain-web-search-count.md @@ -0,0 +1,5 @@ +--- +pypi/posthog: patch +--- + +The LangChain callback handler now records `$ai_web_search_count` for Anthropic responses that report `server_tool_use.web_search_requests`, as the OpenAI, Anthropic and Gemini wrappers and `posthog-js` already do. diff --git a/posthog/ai/langchain/callbacks.py b/posthog/ai/langchain/callbacks.py index 6a50f67f..d0db6ff6 100644 --- a/posthog/ai/langchain/callbacks.py +++ b/posthog/ai/langchain/callbacks.py @@ -719,6 +719,8 @@ def _capture_generation( ) if usage.reasoning_tokens is not None: event_properties["$ai_reasoning_tokens"] = usage.reasoning_tokens + if usage.web_search_count is not None and usage.web_search_count > 0: + event_properties["$ai_web_search_count"] = usage.web_search_count # Generation results generation_result = output.generations[-1] @@ -852,6 +854,23 @@ class ModelUsage: reasoning_tokens: Optional[int] cache_write_5m_tokens: Optional[int] = None cache_write_1h_tokens: Optional[int] = None + web_search_count: Optional[int] = None + + +def _extract_web_search_requests(usage: Union[BaseModel, dict]) -> Optional[int]: + if isinstance(usage, BaseModel): + usage = usage.__dict__ + server_tool_use = usage.get("server_tool_use") + web_search_requests = ( + server_tool_use.get("web_search_requests") + if isinstance(server_tool_use, dict) + else getattr(server_tool_use, "web_search_requests", None) + ) + if isinstance(web_search_requests, int) and not isinstance( + web_search_requests, bool + ): + return web_search_requests + return None def _parse_usage_model( @@ -920,6 +939,11 @@ def _parse_usage_model( ): parsed_usage["reasoning"] = usage["output_token_details"].get("reasoning") + # Anthropic reports the exact number of web searches of a response + web_search_requests = _extract_web_search_requests(usage) + if web_search_requests is not None: + parsed_usage["web_search"] = web_search_requests + field_mapping = { "input": "input_tokens", "output": "output_tokens", @@ -934,6 +958,7 @@ def _parse_usage_model( }, cache_write_5m_tokens=parsed_usage.get("cache_write_5m"), cache_write_1h_tokens=parsed_usage.get("cache_write_1h"), + web_search_count=parsed_usage.get("web_search"), ) # For Anthropic providers, LangChain reports input_tokens as the sum of all input tokens. # Our cost calculation expects them to be separate for Anthropic, so we subtract cache tokens. @@ -971,12 +996,19 @@ def _parse_usage( reasoning_tokens=None, ) + # The generations loop below can replace the llm_output usage, which is the + # only place langchain-anthropic reports server_tool_use, so read it here. + web_search_count: Optional[int] = None + if response.llm_output is not None: for key in llm_usage_keys: if response.llm_output.get(key): llm_usage = _parse_usage_model( response.llm_output[key], provider, model ) + web_search_count = _extract_web_search_requests( + response.llm_output[key] + ) break if hasattr(response, "generations"): @@ -1022,6 +1054,9 @@ def _parse_usage( llm_usage = _parse_usage_model(chunk_usage, provider, model) break + if llm_usage.web_search_count is None: + llm_usage.web_search_count = web_search_count + return llm_usage diff --git a/posthog/test/ai/langchain/test_callbacks.py b/posthog/test/ai/langchain/test_callbacks.py index bf884260..04f8e666 100644 --- a/posthog/test/ai/langchain/test_callbacks.py +++ b/posthog/test/ai/langchain/test_callbacks.py @@ -2951,6 +2951,57 @@ def test_stop_reason_resolution( assert props.get("$ai_stop_reason") == expected +@pytest.mark.parametrize( + "server_tool_use,expected", + [ + # Anthropic reports the exact number of searches + ({"web_search_requests": 2}, 2), + # no search is not worth an event property + ({"web_search_requests": 0}, None), + # the provider reports nothing + (None, None), + ], +) +def test_web_search_count(mock_client, server_tool_use, expected): + from langchain_core.outputs import ChatGeneration, LLMResult + + cb = CallbackHandler(mock_client) + run_id = uuid.uuid4() + cb._set_llm_metadata( + serialized={}, + run_id=run_id, + messages=[{"role": "user", "content": "test"}], + metadata={"ls_provider": "anthropic", "ls_model_name": "claude-sonnet"}, + ) + usage = {"input_tokens": 10, "output_tokens": 5} + if server_tool_use is not None: + usage["server_tool_use"] = server_tool_use + # langchain-anthropic always sets usage_metadata on the message, which the + # generations loop prefers over llm_output["usage"] + response = LLMResult( + generations=[ + [ + ChatGeneration( + message=AIMessage( + content="r", + usage_metadata={ + "input_tokens": 10, + "output_tokens": 5, + "total_tokens": 15, + }, + ) + ) + ] + ], + llm_output={"usage": usage}, + ) + + cb._pop_run_and_capture_generation(run_id, None, response) + + props = mock_client.capture_ai.call_args.kwargs["properties"] + assert props.get("$ai_web_search_count") == expected + + class TestGetHttpStatus: def test_uses_the_status_code_of_an_http_error(self): from posthog.ai.langchain.callbacks import _get_http_status diff --git a/references/public_api_snapshot.txt b/references/public_api_snapshot.txt index 4d3b41e1..6e4b5076 100644 --- a/references/public_api_snapshot.txt +++ b/references/public_api_snapshot.txt @@ -582,6 +582,7 @@ attribute posthog.ai.langchain.callbacks.ModelUsage.cache_write_tokens: Optional attribute posthog.ai.langchain.callbacks.ModelUsage.input_tokens: Optional[int] attribute posthog.ai.langchain.callbacks.ModelUsage.output_tokens: Optional[int] attribute posthog.ai.langchain.callbacks.ModelUsage.reasoning_tokens: Optional[int] +attribute posthog.ai.langchain.callbacks.ModelUsage.web_search_count: Optional[int] = None attribute posthog.ai.langchain.callbacks.RunMetadata = Union[SpanMetadata, GenerationMetadata] attribute posthog.ai.langchain.callbacks.RunMetadataStorage = Dict[UUID, RunMetadata] attribute posthog.ai.langchain.callbacks.SpanMetadata.end_time: Optional[float] @@ -1226,7 +1227,7 @@ class posthog.ai.gemini.gemini_converter.GeminiMessage class posthog.ai.gemini.gemini_converter.GeminiPart class posthog.ai.langchain.callbacks.CallbackHandler(client: Optional[Client] = None, *, distinct_id: Optional[Union[str, int, UUID]] = None, trace_id: Optional[Union[str, int, float, UUID]] = None, properties: Optional[Dict[str, Any]] = None, privacy_mode: bool = False, groups: Optional[Dict[str, Any]] = None) class posthog.ai.langchain.callbacks.GenerationMetadata(name: str, start_time: float, end_time: Optional[float], input: Optional[Any], provider: Optional[str] = None, model: Optional[str] = None, model_params: Optional[Dict[str, Any]] = None, base_url: Optional[str] = None, tools: Optional[List[Dict[str, Any]]] = None, posthog_properties: Optional[Dict[str, Any]] = None) -class posthog.ai.langchain.callbacks.ModelUsage(input_tokens: Optional[int], output_tokens: Optional[int], cache_write_tokens: Optional[int], cache_read_tokens: Optional[int], reasoning_tokens: Optional[int], cache_write_5m_tokens: Optional[int] = None, cache_write_1h_tokens: Optional[int] = None) +class posthog.ai.langchain.callbacks.ModelUsage(input_tokens: Optional[int], output_tokens: Optional[int], cache_write_tokens: Optional[int], cache_read_tokens: Optional[int], reasoning_tokens: Optional[int], cache_write_5m_tokens: Optional[int] = None, cache_write_1h_tokens: Optional[int] = None, web_search_count: Optional[int] = None) class posthog.ai.langchain.callbacks.SpanMetadata(name: str, start_time: float, end_time: Optional[float], input: Optional[Any]) class posthog.ai.langchain.middleware.PostHogMiddleware(client: Optional[Client] = None, *, distinct_id: Optional[Union[str, int, UUID]] = None, trace_id: Optional[Union[str, int, float, UUID]] = None, properties: Optional[dict[str, Any]] = None, privacy_mode: bool = False, groups: Optional[dict[str, Any]] = None) class posthog.ai.openai.openai.OpenAI(posthog_client: Optional[PostHogClient] = None, **kwargs) From de3274f5639abb204784e282611158cd8d657742 Mon Sep 17 00:00:00 2001 From: "releaser-posthog-python[bot]" <262414804+releaser-posthog-python[bot]@users.noreply.github.com> Date: Wed, 28 Oct 2026 09:26:46 +0000 Subject: [PATCH 26/60] chore: Release v7.76.1 [skip ci] (cherry picked from commit a736b43290fc88fecc929916887a3c1c215984a7) --- .sampo/changesets/langchain-web-search-count.md | 5 ----- openfeature-provider/CHANGELOG.md | 6 ++++++ openfeature-provider/pyproject.toml | 2 +- posthog/CHANGELOG.md | 6 ++++++ posthog/version.py | 2 +- pyproject.toml | 2 +- uv.lock | 4 ++-- 7 files changed, 17 insertions(+), 10 deletions(-) delete mode 100644 .sampo/changesets/langchain-web-search-count.md diff --git a/.sampo/changesets/langchain-web-search-count.md b/.sampo/changesets/langchain-web-search-count.md deleted file mode 100644 index 465d4d91..00000000 --- a/.sampo/changesets/langchain-web-search-count.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -pypi/posthog: patch ---- - -The LangChain callback handler now records `$ai_web_search_count` for Anthropic responses that report `server_tool_use.web_search_requests`, as the OpenAI, Anthropic and Gemini wrappers and `posthog-js` already do. diff --git a/openfeature-provider/CHANGELOG.md b/openfeature-provider/CHANGELOG.md index 599b78d5..6490a4fd 100644 --- a/openfeature-provider/CHANGELOG.md +++ b/openfeature-provider/CHANGELOG.md @@ -4,6 +4,12 @@ All notable changes to `openfeature-provider-posthog` are documented here. This file is maintained by [Sampo](https://github.com/bruits/sampo) from changesets in `.sampo/changesets/` that target `pypi/openfeature-provider-posthog`. +## 0.1.127 — 2026-10-28 + +### Patch changes + +- Updated dependencies: posthog@7.76.1 + ## 0.1.126 — 2026-10-27 ### Patch changes diff --git a/openfeature-provider/pyproject.toml b/openfeature-provider/pyproject.toml index 96b2e73e..bf8a48f0 100644 --- a/openfeature-provider/pyproject.toml +++ b/openfeature-provider/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "openfeature-provider-posthog" -version = "0.1.126" +version = "0.1.127" description = "Official PostHog provider for the OpenFeature Python SDK." readme = "README.md" authors = [{ name = "PostHog", email = "engineering@posthog.com" }] diff --git a/posthog/CHANGELOG.md b/posthog/CHANGELOG.md index de469630..142ff213 100644 --- a/posthog/CHANGELOG.md +++ b/posthog/CHANGELOG.md @@ -1,5 +1,11 @@ # posthog +## 7.76.1 — 2026-10-28 + +### Patch changes + +- [b0a2b59](https://github.com/posthog/posthog-python/commit/b0a2b59dc09e96903cdf97ea3c8ab5298bdff7bb) The LangChain callback handler now records `$ai_web_search_count` for Anthropic responses that report `server_tool_use.web_search_requests`, as the OpenAI, Anthropic and Gemini wrappers and `posthog-js` already do. — Thanks @pcbeingused333! + ## 7.76.0 — 2026-10-27 ### Minor changes diff --git a/posthog/version.py b/posthog/version.py index 7fae9282..4d481543 100644 --- a/posthog/version.py +++ b/posthog/version.py @@ -1 +1 @@ -VERSION = "7.76.0" +VERSION = "7.76.1" diff --git a/pyproject.toml b/pyproject.toml index 81cac583..09c9338a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "posthog" -version = "7.76.0" +version = "7.76.1" description = "Integrate PostHog into any python application." authors = [{ name = "PostHog", email = "engineering@posthog.com" }] maintainers = [{ name = "PostHog", email = "engineering@posthog.com" }] diff --git a/uv.lock b/uv.lock index eba651da..806452ad 100644 --- a/uv.lock +++ b/uv.lock @@ -2656,7 +2656,7 @@ wheels = [ [[package]] name = "openfeature-provider-posthog" -version = "0.1.126" +version = "0.1.127" source = { editable = "openfeature-provider" } dependencies = [ { name = "openfeature-sdk" }, @@ -3045,7 +3045,7 @@ wheels = [ [[package]] name = "posthog" -version = "7.76.0" +version = "7.76.1" source = { editable = "." } dependencies = [ { name = "distro" }, From 4ad74e5e58e870c6cbb6ceed38bd4c2f67e545b4 Mon Sep 17 00:00:00 2001 From: "releaser-posthog-python[bot]" <262414804+releaser-posthog-python[bot]@users.noreply.github.com> Date: Wed, 28 Oct 2026 09:26:52 +0000 Subject: [PATCH 27/60] Update generated references (cherry picked from commit 3044d8ed5919b6b11b42642359651855c6f9ec38) --- .../posthog-python-references-7.76.1.json | 3548 +++++++++++++++++ .../posthog-python-references-latest.json | 2 +- 2 files changed, 3549 insertions(+), 1 deletion(-) create mode 100644 references/posthog-python-references-7.76.1.json diff --git a/references/posthog-python-references-7.76.1.json b/references/posthog-python-references-7.76.1.json new file mode 100644 index 00000000..966c6f29 --- /dev/null +++ b/references/posthog-python-references-7.76.1.json @@ -0,0 +1,3548 @@ +{ + "id": "posthog-python", + "hogRef": "0.3", + "info": { + "version": "7.76.1", + "id": "posthog-python", + "title": "PostHog Python SDK", + "description": "Integrate PostHog into any python application.", + "slugPrefix": "posthog-python", + "specUrl": "https://github.com/PostHog/posthog-python" + }, + "types": [ + { + "id": "FeatureFlag", + "name": "FeatureFlag", + "path": "posthog.types.FeatureFlag", + "properties": [ + { + "name": "key", + "type": "str", + "description": "Field: key" + }, + { + "name": "enabled", + "type": "bool", + "description": "Field: enabled" + }, + { + "name": "variant", + "type": "Optional[str]", + "description": "Field: variant" + }, + { + "name": "reason", + "type": "Optional[FlagReason]", + "description": "Field: reason" + }, + { + "name": "metadata", + "type": "Union[FlagMetadata, LegacyFlagMetadata]", + "description": "Field: metadata" + } + ], + "example": "" + }, + { + "id": "FeatureFlagResult", + "name": "FeatureFlagResult", + "path": "posthog.types.FeatureFlagResult", + "properties": [ + { + "name": "key", + "type": "str", + "description": "Field: key" + }, + { + "name": "enabled", + "type": "bool", + "description": "Field: enabled" + }, + { + "name": "variant", + "type": "Optional[str]", + "description": "Field: variant" + }, + { + "name": "payload", + "type": "Optional[Any]", + "description": "Field: payload" + }, + { + "name": "reason", + "type": "Optional[str]", + "description": "Field: reason" + } + ], + "example": "" + }, + { + "id": "FlagMetadata", + "name": "FlagMetadata", + "path": "posthog.types.FlagMetadata", + "properties": [ + { + "name": "id", + "type": "int", + "description": "Field: id" + }, + { + "name": "payload", + "type": "Optional[str]", + "description": "Field: payload" + }, + { + "name": "version", + "type": "int", + "description": "Field: version" + }, + { + "name": "description", + "type": "str", + "description": "Field: description" + }, + { + "name": "has_experiment", + "type": "Optional[bool]", + "description": "Field: has_experiment" + } + ], + "example": "" + }, + { + "id": "FlagReason", + "name": "FlagReason", + "path": "posthog.types.FlagReason", + "properties": [ + { + "name": "code", + "type": "str", + "description": "Field: code" + }, + { + "name": "condition_index", + "type": "Optional[int]", + "description": "Field: condition_index" + }, + { + "name": "description", + "type": "str", + "description": "Field: description" + } + ], + "example": "" + }, + { + "id": "FlagsAndPayloads", + "name": "FlagsAndPayloads", + "path": "posthog.types.FlagsAndPayloads", + "properties": [ + { + "name": "featureFlags", + "type": "Optional[dict[str, Union[bool, str]]]", + "description": "Field: featureFlags" + }, + { + "name": "featureFlagPayloads", + "type": "Optional[dict[str, Any]]", + "description": "Field: featureFlagPayloads" + } + ], + "example": "" + }, + { + "id": "FlagsResponse", + "name": "FlagsResponse", + "path": "posthog.types.FlagsResponse", + "properties": [ + { + "name": "flags", + "type": "dict[str, FeatureFlag]", + "description": "Field: flags" + }, + { + "name": "errorsWhileComputingFlags", + "type": "bool", + "description": "Field: errorsWhileComputingFlags" + }, + { + "name": "requestId", + "type": "str", + "description": "Field: requestId" + }, + { + "name": "quotaLimit", + "type": "Optional[list[str]]", + "description": "Field: quotaLimit" + }, + { + "name": "evaluatedAt", + "type": "Optional[int]", + "description": "Field: evaluatedAt" + }, + { + "name": "minimalFlagCalledEvents", + "type": "bool", + "description": "Field: minimalFlagCalledEvents" + } + ], + "example": "" + }, + { + "id": "LegacyFlagMetadata", + "name": "LegacyFlagMetadata", + "path": "posthog.types.LegacyFlagMetadata", + "properties": [ + { + "name": "payload", + "type": "Any", + "description": "Field: payload" + } + ], + "example": "" + }, + { + "id": "SendFeatureFlagsOptions", + "name": "SendFeatureFlagsOptions", + "path": "posthog.types.SendFeatureFlagsOptions", + "properties": [ + { + "name": "should_send", + "type": "bool", + "description": "Field: should_send" + }, + { + "name": "only_evaluate_locally", + "type": "Optional[bool]", + "description": "Field: only_evaluate_locally" + }, + { + "name": "person_properties", + "type": "Optional[dict[str, Any]]", + "description": "Field: person_properties" + }, + { + "name": "group_properties", + "type": "Optional[dict[str, dict[str, Any]]]", + "description": "Field: group_properties" + }, + { + "name": "flag_keys_filter", + "type": "Optional[list[str]]", + "description": "Field: flag_keys_filter" + } + ], + "example": "" + }, + { + "id": "OptionalCaptureArgs", + "name": "OptionalCaptureArgs", + "path": "posthog.args.OptionalCaptureArgs", + "properties": [ + { + "name": "distinct_id", + "type": "NotRequired[Union[Number, str, UUID, int, any]]", + "description": "Field: distinct_id" + }, + { + "name": "properties", + "type": "NotRequired[Optional[dict[str, Any]]]", + "description": "Field: properties" + }, + { + "name": "timestamp", + "type": "NotRequired[Union[datetime, str, any]]", + "description": "Field: timestamp" + }, + { + "name": "uuid", + "type": "NotRequired[Union[str, UUID, any]]", + "description": "Field: uuid" + }, + { + "name": "groups", + "type": "NotRequired[Optional[dict[str, str]]]", + "description": "Field: groups" + }, + { + "name": "flags", + "type": "NotRequired[Optional[ForwardRef('FeatureFlagEvaluations')]]", + "description": "Field: flags" + }, + { + "name": "send_feature_flags", + "type": "NotRequired[Union[bool, SendFeatureFlagsOptions, any]]", + "description": "Field: send_feature_flags" + }, + { + "name": "disable_geoip", + "type": "NotRequired[Optional[bool]]", + "description": "Field: disable_geoip" + }, + { + "name": "_property_allowlist", + "type": "NotRequired[Optional[frozenset[str]]]", + "description": "Field: _property_allowlist" + } + ], + "example": "" + }, + { + "id": "OptionalSetArgs", + "name": "OptionalSetArgs", + "path": "posthog.args.OptionalSetArgs", + "properties": [ + { + "name": "distinct_id", + "type": "NotRequired[Union[Number, str, UUID, int, any]]", + "description": "Field: distinct_id" + }, + { + "name": "properties", + "type": "NotRequired[Optional[dict[str, Any]]]", + "description": "Field: properties" + }, + { + "name": "timestamp", + "type": "NotRequired[Union[datetime, str, any]]", + "description": "Field: timestamp" + }, + { + "name": "uuid", + "type": "NotRequired[Union[str, UUID, any]]", + "description": "Field: uuid" + }, + { + "name": "disable_geoip", + "type": "NotRequired[Optional[bool]]", + "description": "Field: disable_geoip" + } + ], + "example": "" + }, + { + "id": "SendFeatureFlagsOptions", + "name": "SendFeatureFlagsOptions", + "path": "posthog.types.SendFeatureFlagsOptions", + "properties": [ + { + "name": "should_send", + "type": "bool", + "description": "Field: should_send" + }, + { + "name": "only_evaluate_locally", + "type": "Optional[bool]", + "description": "Field: only_evaluate_locally" + }, + { + "name": "person_properties", + "type": "Optional[dict[str, Any]]", + "description": "Field: person_properties" + }, + { + "name": "group_properties", + "type": "Optional[dict[str, dict[str, Any]]]", + "description": "Field: group_properties" + }, + { + "name": "flag_keys_filter", + "type": "Optional[list[str]]", + "description": "Field: flag_keys_filter" + } + ], + "example": "" + } + ], + "classes": [ + { + "id": "PostHog", + "title": "PostHog", + "description": "This is the SDK reference for the PostHog Python SDK. You can learn more about example usage in the [Python SDK documentation](/docs/libraries/python). You can also follow [Flask](/docs/libraries/flask) and [Django](/docs/libraries/django) guides to integrate PostHog into your project. For long-running applications, create one client during application startup and reuse it for the lifetime of the process. This keeps background queues predictable and makes shutdown flushing straightforward. Multiple clients are still supported for intentional multi-project or multi-host setups.", + "functions": [ + { + "id": "__init__", + "title": "Client", + "description": "Initialize a new PostHog client instance.", + "details": "", + "category": "Initialization", + "params": [ + { + "name": "project_api_key", + "description": "PostHog project API key/token.", + "isOptional": true, + "type": "str" + }, + { + "name": "host", + "description": "PostHog host. Defaults to the US ingestion endpoint when not set. App hosts such as ``https://us.posthog.com`` are mapped to the corresponding ingestion host.", + "isOptional": false, + "type": "any" + }, + { + "name": "debug", + "description": "Enable verbose SDK logging and re-raise errors from public API methods.", + "isOptional": false, + "type": "bool" + }, + { + "name": "max_queue_size", + "description": "Maximum number of events buffered before upload.", + "isOptional": false, + "type": "int" + }, + { + "name": "send", + "description": "If False, queueing succeeds but events are not sent.", + "isOptional": false, + "type": "bool" + }, + { + "name": "on_error", + "description": "Optional callback invoked by background consumers when an upload fails. Keep it short and non-blocking. Calling lifecycle methods directly is safe and deferred, but do not start another thread or task that calls ``flush()``, ``join()``, or ``shutdown()`` and then wait for it from the callback.", + "isOptional": false, + "type": "any" + }, + { + "name": "flush_at", + "description": "Number of queued events that triggers a batch upload.", + "isOptional": false, + "type": "int" + }, + { + "name": "flush_interval", + "description": "Maximum seconds a background consumer waits before flushing a partial batch.", + "isOptional": false, + "type": "float" + }, + { + "name": "gzip", + "description": "Whether to gzip event upload payloads.", + "isOptional": false, + "type": "bool" + }, + { + "name": "max_retries", + "description": "Number of upload retries. Values below 0 are treated as 0.", + "isOptional": false, + "type": "int" + }, + { + "name": "sync_mode", + "description": "If True, send each event synchronously instead of using background worker threads. This blocks the calling thread; in asyncio applications such as FastAPI, use ``AsyncPosthog`` instead.", + "isOptional": false, + "type": "bool" + }, + { + "name": "timeout", + "description": "HTTP request timeout in seconds for event uploads.", + "isOptional": false, + "type": "float" + }, + { + "name": "thread", + "description": "Number of background consumer threads.", + "isOptional": false, + "type": "int" + }, + { + "name": "poll_interval", + "description": "Seconds between local feature flag definition refreshes.", + "isOptional": false, + "type": "int" + }, + { + "name": "personal_api_key", + "description": "Deprecated alias for ``secret_key``. Still honored for backwards compatibility; prefer ``secret_key``, which also accepts a Project Secret API Key.", + "isOptional": false, + "type": "any" + }, + { + "name": "disabled", + "description": "If True, disable captures and API requests. Useful in tests.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable server-side GeoIP enrichment. Defaults to True.", + "isOptional": false, + "type": "bool" + }, + { + "name": "is_server", + "description": "Whether events are emitted from a server-side runtime. Defaults to True; set to False when using the SDK as a client/CLI so the device OS is attributed to the person normally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "historical_migration", + "description": "Mark events as historical migration imports.", + "isOptional": false, + "type": "bool" + }, + { + "name": "feature_flags_request_timeout_seconds", + "description": "Timeout in seconds for feature flag and remote config requests.", + "isOptional": false, + "type": "float" + }, + { + "name": "feature_flags_request_max_retries", + "description": "Number of retries for feature flag requests after network, transport, or timeout failures. Defaults to 1. Set to 0 to disable retries.", + "isOptional": false, + "type": "int" + }, + { + "name": "super_properties", + "description": "Properties merged into every captured event.", + "isOptional": false, + "type": "any" + }, + { + "name": "enable_exception_autocapture", + "description": "Automatically capture uncaught exceptions.", + "isOptional": false, + "type": "bool" + }, + { + "name": "log_captured_exceptions", + "description": "Also log exceptions captured by error tracking.", + "isOptional": false, + "type": "bool" + }, + { + "name": "project_root", + "description": "Root path used to determine in-app stack frames for captured exceptions. Defaults to the current working directory.", + "isOptional": false, + "type": "any" + }, + { + "name": "privacy_mode", + "description": "For AI observability, capture usage metadata without prompt inputs or outputs.", + "isOptional": false, + "type": "bool" + }, + { + "name": "before_send", + "description": "Optional callback that can modify or drop events before upload. Return ``None`` to drop an event.", + "isOptional": false, + "type": "any" + }, + { + "name": "flag_fallback_cache_url", + "description": "Optional feature flag fallback cache URL, such as ``memory://local/?ttl=300&size=10000`` or a Redis URL.", + "isOptional": false, + "type": "any" + }, + { + "name": "enable_local_evaluation", + "description": "Whether to poll feature flag definitions for local evaluation when a personal API key is configured.", + "isOptional": false, + "type": "bool" + }, + { + "name": "flag_definition_cache_provider", + "description": "Optional external cache provider for sharing feature flag definitions across workers.", + "isOptional": true, + "type": "FlagDefinitionCacheProvider" + }, + { + "name": "capture_exception_code_variables", + "description": "Capture local variable values on exception stack frames.", + "isOptional": false, + "type": "bool" + }, + { + "name": "code_variables_mask_patterns", + "description": "Variable-name patterns to mask when capturing code variables.", + "isOptional": false, + "type": "any" + }, + { + "name": "code_variables_ignore_patterns", + "description": "Variable-name patterns to omit when capturing code variables.", + "isOptional": false, + "type": "any" + }, + { + "name": "code_variables_mask_url_credentials", + "description": "Scrub credentials embedded in URLs/DSNs (e.g. ``user:pass@host``) from captured code variables, regardless of the surrounding variable name. Defaults to True.", + "isOptional": false, + "type": "any" + }, + { + "name": "code_variables_detect_secrets", + "description": "Last-resort entropy-based detection that redacts high-entropy secret-looking values (API keys, tokens, strong passwords) sitting in innocuously-named variables, after the name and URL checks. Skips structured ids (UUIDs, ObjectIds, hashes). Defaults to True.", + "isOptional": false, + "type": "any" + }, + { + "name": "in_app_modules", + "description": "Module/package prefixes treated as in-app frames in captured exceptions.", + "isOptional": false, + "type": "UnionType[list[str], any]" + }, + { + "name": "enable_exception_autocapture_rate_limiting", + "description": "Rate limit autocaptured exceptions client-side with a token bucket per exception type. Disabled by default.", + "isOptional": false, + "type": "bool" + }, + { + "name": "exception_autocapture_bucket_size", + "description": "Maximum burst of autocaptured exceptions allowed per exception type (token bucket size, clamped to 0-100).", + "isOptional": false, + "type": "int" + }, + { + "name": "exception_autocapture_refill_rate", + "description": "Tokens restored per refill interval for each exception type's bucket.", + "isOptional": false, + "type": "int" + }, + { + "name": "exception_autocapture_refill_interval_seconds", + "description": "Seconds between token refills for autocaptured exception rate limiting.", + "isOptional": false, + "type": "int" + }, + { + "name": "capture_mode", + "description": "Capture wire protocol to use. Defaults to ``CaptureMode.V0`` (legacy ``/batch/``). Set ``CaptureMode.V1`` (or pass the string ``\"v1\"``) to opt into ``/i/v1/analytics/events``. When omitted, the ``POSTHOG_CAPTURE_MODE`` env var is consulted, then ``V0``.", + "isOptional": false, + "type": "CaptureMode" + }, + { + "name": "capture_compression", + "description": "Request-body compression for capture-v1 uploads (ignored in V0, which uses ``gzip``). ``CaptureCompression.GZIP`` or ``DEFLATE`` (or the strings ``\"gzip\"``/``\"deflate\"``). When omitted, the ``POSTHOG_CAPTURE_COMPRESSION`` env var is consulted, then the legacy ``gzip`` flag, then no compression.", + "isOptional": false, + "type": "CaptureCompression" + }, + { + "name": "secret_key", + "description": "A Personal API Key or Project Secret API Key, used to authenticate local feature flag evaluation, remote config payloads, and decrypted flag payloads. Example:: posthog.Client(project_api_key, secret_key=\"phx_...\")", + "isOptional": false, + "type": "any" + }, + { + "name": "metrics", + "description": "", + "isOptional": true, + "type": "dict" + }, + { + "name": "enable_full_ai_capture", + "description": "Route PostHog AI wrapper events through the dedicated AI capture endpoint and capture full AI content: skips string truncation and passes media (base64/data URIs) through unredacted. ``privacy_mode`` always wins. Defaults to False.", + "isOptional": false, + "type": "bool" + }, + { + "name": "capture_trace_context", + "description": "When OpenTelemetry is installed and a valid span is active at capture time, add its trace and span IDs as ``$trace_id`` and ``$span_id`` properties to events captured with ``capture()`` and ``capture_ai()``, so they can be correlated with backend traces. Explicit ``$trace_id``/``$span_id`` values passed in ``properties`` win. Exception events (``capture_exception``) always attach these IDs regardless of this setting. Defaults to False.", + "isOptional": false, + "type": "bool" + }, + { + "name": "_use_ai_lane", + "description": "", + "isOptional": false, + "type": "bool" + }, + { + "name": "_enable_multimodal_capture", + "description": "", + "isOptional": false, + "type": "bool" + }, + { + "name": "traces", + "description": "Config dict for distributed tracing: ``service_name``, ``service_version``, ``environment``, ``resource_attributes``, ``flush_interval`` (5 s), ``max_queue_size`` (2048), ``max_export_batch_size`` (512), ``max_live_spans`` (10000), ``max_span_age`` (3600 s), ``max_attributes_per_span`` (128), ``max_events_per_span`` (128), ``max_attribute_value_length`` (8192). ``before_span_send`` is a callable, or a list run in order, that receives each finished span as a dict (``trace_id``, ``span_id`` and ``parent_span_id`` are read-only) and returns it, edited, or ``None`` to drop it; a hook that raises drops the span. Tracing is off until this is provided. Spans export on a background timer even with ``sync_mode``; serverless handlers should call ``flush()`` before returning. Defaults to None.", + "isOptional": true, + "type": "dict" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import Posthog\n\nposthog = Posthog('', host='')" + } + ] + }, + { + "id": "alias", + "title": "alias", + "description": "Create an alias between two distinct IDs.", + "details": "", + "category": "Identification", + "params": [ + { + "name": "previous_id", + "description": "The previous distinct ID. Required - the call is dropped with a warning if it is missing or empty.", + "isOptional": true, + "type": "Number" + }, + { + "name": "distinct_id", + "description": "The new distinct ID to alias to. Falls back to the context distinct ID; the call is dropped with a warning if neither is available.", + "isOptional": true, + "type": "str" + }, + { + "name": "timestamp", + "description": "The timestamp of the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC.", + "isOptional": false, + "type": "datetime" + }, + { + "name": "uuid", + "description": "A unique identifier for the event. If provided, it must be a valid UUID string or uuid.UUID instance; invalid values are ignored and replaced with a newly generated UUID.", + "isOptional": true, + "type": "str" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this event.", + "isOptional": true, + "type": "bool" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.alias(previous_id='distinct_id', distinct_id='alias_id')" + } + ] + }, + { + "id": "capture", + "title": "capture", + "description": "Captures an event manually. [Learn about capture best practices](https://posthog.com/docs/product-analytics/capture-events)", + "details": "", + "category": "Capture", + "params": [ + { + "name": "event", + "description": "The event name to capture.", + "isOptional": true, + "type": "str" + }, + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalCaptureArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Anonymous event", + "code": "# Anonymous event\nposthog.capture('some-anon-event')" + }, + { + "id": "example_2", + "name": "Context usage", + "code": "# Context usage\nfrom posthog import identify_context, new_context\nwith new_context():\n identify_context('distinct_id_of_the_user')\n posthog.capture('user_signed_up')\n posthog.capture('user_logged_in')\n posthog.capture('some-custom-action', distinct_id='distinct_id_of_the_user')" + }, + { + "id": "example_3", + "name": "Set event properties", + "code": "# Set event properties\nposthog.capture(\n \"user_signed_up\",\n distinct_id=\"distinct_id_of_the_user\",\n properties={\n \"login_type\": \"email\",\n \"is_free_trial\": \"true\"\n }\n)" + }, + { + "id": "example_4", + "name": "Page view event", + "code": "# Page view event\nposthog.capture('$pageview', distinct_id=\"distinct_id_of_the_user\", properties={'$current_url': 'https://example.com'})" + } + ] + }, + { + "id": "capture_ai", + "title": "capture_ai", + "description": "Capture an AI event on the dedicated AI capture endpoint. Beta: the signature is stable; operational limits (per-event size cap, batching, endpoint) may change without notice. Takes the same arguments and returns the same value as `capture()`: the event UUID, or None when the event was not admitted (disabled client, or dropped by `before_send`). The event is queued on an isolated AI lane with its own consumer pool and a higher per-event size cap, posting to the dedicated AI ingestion endpoint. The payload is sent as given \u2014 no redaction or truncation is applied here.", + "details": "", + "category": "Capture", + "params": [ + { + "name": "event", + "description": "", + "isOptional": true, + "type": "str" + }, + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalCaptureArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + } + }, + { + "id": "capture_exception", + "title": "capture_exception", + "description": "Capture an exception for error tracking. When OpenTelemetry is installed and a valid span is active, its trace and span IDs are added as ``$trace_id`` and ``$span_id`` event properties.", + "details": "", + "category": "Error Tracking", + "params": [ + { + "name": "exception", + "description": "The exception to capture.", + "isOptional": true, + "type": "BaseException" + }, + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalCaptureArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "try:\n # Some code that might fail\n pass\nexcept Exception as e:\n posthog.capture_exception(e, 'user_distinct_id', properties=additional_properties)" + } + ] + }, + { + "id": "evaluate_flags", + "title": "evaluate_flags", + "description": "Evaluate all feature flags for a user in a single call and return a :class:`FeatureFlagEvaluations` snapshot. Branch on ``.is_enabled()`` / ``.get_flag()`` and pass the same snapshot to :meth:`capture` via the ``flags`` option so events carry the exact flag values the code branched on. Prefer this over repeated ``get_feature_flag()`` calls and over ``capture(send_feature_flags=True)`` \u2014 it consolidates flag evaluation into a single ``/flags`` request per incoming request. Local evaluation is transparent: when the poller resolves a flag, the snapshot's ``$feature_flag_called`` events are tagged ``locally_evaluated=True`` and reason ``\"Evaluated locally\"``.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The user's distinct ID. If ``None``, falls back to the context distinct_id. If still unresolvable, returns an empty snapshot.", + "isOptional": false, + "type": "Number" + }, + { + "name": "groups", + "description": "Mapping of group type to group key.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "Person properties to use for evaluation.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "Group properties keyed by group type.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "If True, never fall back to remote evaluation \u2014 flags that can't be evaluated locally are simply omitted from the snapshot.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup.", + "isOptional": true, + "type": "bool" + }, + { + "name": "flag_keys", + "description": "Optional list that scopes local evaluation, the underlying ``/flags`` request, and the returned snapshot. When omitted or ``None``, all flags are evaluated. An empty list returns an empty snapshot without evaluating flags. A requested key absent from loaded local definitions is included in one remote fallback per ``evaluate_flags`` call unless ``only_evaluate_locally`` is True. If the server also does not know the key, it is omitted from the snapshot.", + "isOptional": true, + "type": "list[str]" + }, + { + "name": "device_id", + "description": "Optional device ID override. If not provided, falls back to the context device_id (which may be set via tracing headers). Used by experience-continuity flags to match users across distinct_id changes.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "FeatureFlagEvaluations" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "flags = posthog.evaluate_flags(\n \"user_123\",\n person_properties={\"plan\": \"enterprise\"},\n)\nif flags.is_enabled(\"new-dashboard\"):\n render_new_dashboard()\nposthog.capture(\"page_viewed\", distinct_id=\"user_123\", flags=flags)" + } + ] + }, + { + "id": "feature_enabled", + "title": "feature_enabled", + "description": "Check if a feature flag is enabled for a user.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "The feature flag key.", + "isOptional": true, + "type": "str" + }, + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to only evaluate locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "send_feature_flag_events", + "description": "Whether to send feature flag events.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[bool]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "is_my_flag_enabled = posthog.feature_enabled('flag-key', 'distinct_id_of_your_user')\nif is_my_flag_enabled:\n # Do something differently for this user\n # Optional: fetch the payload\n matched_flag_payload = posthog.get_feature_flag_payload('flag-key', 'distinct_id_of_your_user')" + } + ] + }, + { + "id": "feature_flag_definitions", + "title": "feature_flag_definitions", + "description": "Return feature flag definitions loaded for local evaluation. Returns: The currently loaded feature flag definitions, or ``None`` before local evaluation has loaded definitions.", + "details": "", + "category": "Feature flags", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "flush", + "title": "flush", + "description": "Attempt immediate delivery of queued events and ended spans, keeping the client reusable. For a client shared across requests or serverless invocations (including the module-level client), call ``flush()`` before a short-lived invocation returns. Use ``shutdown()`` instead only for final process cleanup or when disposing of a client that will not be reused. Flush does not stop the background workers or feature flag poller, and does not end open spans. Returning does not guarantee server receipt: pending work can remain after a timeout or span export failure, and failed events may be dropped by the consumers. Calls made directly from SDK callbacks such as ``on_error`` are deferred rather than blocking the worker that invoked the callback.", + "details": "", + "category": null, + "params": [ + { + "name": "timeout_seconds", + "description": "Seconds to wait for event queues to drain, shared across event lanes. Defaults to 10 seconds. Pass ``None`` to wait without a time limit; this does not guarantee successful delivery. Queued spans flush concurrently. The span exporter waits up to this many seconds for an in-flight flush, then uses a fresh drain budget once it acquires the lock. If the lock wait times out, the in-flight flush is left to finish. At least one span request is attempted after acquiring the lock, even with no budget left; retriable failures are retried after backoff while budget remains, with one last attempt at the deadline. Each span request is bounded by the client's ``timeout`` in seconds, and the last request is not cut short. This is therefore not a strict wall-clock limit for the whole call. With ``None``, a retriable span failure is left for a later flush rather than retried within this call.", + "isOptional": true, + "type": "float" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.capture('event_name', distinct_id='user_id')\n# Before a serverless invocation returns; keep the shared client open.\nposthog.flush(timeout_seconds=10)" + } + ] + }, + { + "id": "get_active_span", + "title": "get_active_span", + "description": "The span that is active in the current context, or ``None``. Alpha. Only entering a span (``with posthog.start_span(...) as span:``) makes it active; a span started manually is not. Use it to propagate the trace to the next service: ``span.traceparent()`` is the header value.", + "details": "", + "category": "Tracing", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[Span]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "span = posthog.get_active_span()\nif span is not None:\n headers[\"traceparent\"] = span.traceparent()" + } + ] + }, + { + "id": "get_all_flags", + "title": "get_all_flags", + "description": "Get all feature flags for a user.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to only evaluate locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "flag_keys_to_evaluate", + "description": "A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance.", + "isOptional": true, + "type": "list[str]" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[dict[str, Union[bool, str]]]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.get_all_flags('distinct_id_of_your_user')" + } + ] + }, + { + "id": "get_all_flags_and_payloads", + "title": "get_all_flags_and_payloads", + "description": "Get all feature flags and their payloads for a user.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to only evaluate locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "flag_keys_to_evaluate", + "description": "A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance.", + "isOptional": true, + "type": "list[str]" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "FlagsAndPayloads" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.get_all_flags_and_payloads('distinct_id_of_your_user')" + } + ] + }, + { + "id": "get_feature_flag", + "title": "get_feature_flag", + "description": "Get multivariate feature flag value for a user.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "The feature flag key.", + "isOptional": true, + "type": "str" + }, + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to only evaluate locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "send_feature_flag_events", + "description": "Whether to send feature flag events.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Union[bool, str, any]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "enabled_variant = posthog.get_feature_flag('flag-key', 'distinct_id_of_your_user')\nif enabled_variant == 'variant-key': # replace 'variant-key' with the key of your variant\n # Do something differently for this user\n # Optional: fetch the payload\n matched_flag_payload = posthog.get_feature_flag_payload('flag-key', 'distinct_id_of_your_user')" + } + ] + }, + { + "id": "get_feature_flag_evaluation_runtime", + "title": "get_feature_flag_evaluation_runtime", + "description": "Return where a locally loaded feature flag is meant to be evaluated.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "The feature flag key.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[FeatureFlagEvaluationRuntime]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import FeatureFlagEvaluationRuntime\n\nruntime = posthog.get_feature_flag_evaluation_runtime(\"my-flag\")\nif runtime is FeatureFlagEvaluationRuntime.SERVER:\n ..." + } + ] + }, + { + "id": "get_feature_flag_keys_by_evaluation_runtime", + "title": "get_feature_flag_keys_by_evaluation_runtime", + "description": "Return the keys of locally loaded flags that a runtime can evaluate. A flag set to ``FeatureFlagEvaluationRuntime.ALL`` suits either runtime, so it is returned for ``CLIENT`` and for ``SERVER``, and asking for ``ALL`` returns every loaded flag. Use this to decide which flags to hand to a browser when a backend serves flags to its own frontend.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "evaluation_runtime", + "description": "The runtime to match, as a ``FeatureFlagEvaluationRuntime`` or its string value.", + "isOptional": true, + "type": "FeatureFlagEvaluationRuntime" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "list[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import FeatureFlagEvaluationRuntime\n\nclient_keys = posthog.get_feature_flag_keys_by_evaluation_runtime(\n FeatureFlagEvaluationRuntime.CLIENT\n)" + } + ] + }, + { + "id": "get_feature_flag_payload", + "title": "get_feature_flag_payload", + "description": "Get the payload for a feature flag.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "The feature flag key.", + "isOptional": true, + "type": "str" + }, + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "match_value", + "description": "The specific flag value to get payload for.", + "isOptional": false, + "type": "bool" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to only evaluate locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "send_feature_flag_events", + "description": "Deprecated. Use get_feature_flag() instead if you need events.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[object]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "is_my_flag_enabled = posthog.feature_enabled('flag-key', 'distinct_id_of_your_user')\n\nif is_my_flag_enabled:\n # Do something differently for this user\n # Optional: fetch the payload\n matched_flag_payload = posthog.get_feature_flag_payload('flag-key', 'distinct_id_of_your_user')" + } + ] + }, + { + "id": "get_feature_flag_result", + "title": "get_feature_flag_result", + "description": "Get a FeatureFlagResult object which contains the flag result and payload for a key by evaluating locally or remotely depending on whether local evaluation is enabled and the flag can be locally evaluated. This also captures the `$feature_flag_called` event unless `send_feature_flag_events` is `False`.", + "details": "", + "category": null, + "params": [ + { + "name": "key", + "description": "The feature flag key.", + "isOptional": true, + "type": "str" + }, + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to only evaluate locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "send_feature_flag_events", + "description": "Whether to send feature flag events.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[FeatureFlagResult]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "flag_result = posthog.get_feature_flag_result('flag-key', 'distinct_id_of_your_user')\nif flag_result and flag_result.get_value() == 'variant-key':\n # Do something differently for this user\n # Optional: fetch the payload\n matched_flag_payload = flag_result.payload" + } + ] + }, + { + "id": "get_feature_flags_and_payloads", + "title": "get_feature_flags_and_payloads", + "description": "Get feature flags and payloads for a user.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "flag_keys_to_evaluate", + "description": "A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance.", + "isOptional": true, + "type": "list[str]" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "FlagsAndPayloads" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "result = posthog.get_feature_flags_and_payloads('')" + } + ] + }, + { + "id": "get_feature_payloads", + "title": "get_feature_payloads", + "description": "Get feature flag payloads for a user, preserving valid serialized JSON.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "flag_keys_to_evaluate", + "description": "A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance.", + "isOptional": true, + "type": "list[str]" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "dict[str, Optional[str]]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "payloads = posthog.get_feature_payloads('')" + } + ] + }, + { + "id": "get_feature_variants", + "title": "get_feature_variants", + "description": "Get feature flag variants for a user.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "flag_keys_to_evaluate", + "description": "A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance.", + "isOptional": true, + "type": "list[str]" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "dict[str, Union[bool, str]]" + } + }, + { + "id": "get_flags_decision", + "title": "get_flags_decision", + "description": "Get feature flags decision.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": false, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "flag_keys_to_evaluate", + "description": "A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance.", + "isOptional": true, + "type": "list[str]" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "FlagsResponse" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "decision = posthog.get_flags_decision('user123')" + } + ] + }, + { + "id": "get_remote_config_payload", + "title": "get_remote_config_payload", + "description": "Get the payload for a remote config feature flag.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "The remote config feature flag key.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "get_tags", + "title": "get_tags", + "description": "Get all tags from the current context. Returns: Dict of all tags in the current context.", + "details": "", + "category": "Contexts", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "dict[str, Any]" + } + }, + { + "id": "group_identify", + "title": "group_identify", + "description": "Identify a group and set its properties.", + "details": "", + "category": "Identification", + "params": [ + { + "name": "group_type", + "description": "The type of group (e.g., 'company', 'team'). Required - the call is dropped with a warning if it is missing or empty.", + "isOptional": true, + "type": "str" + }, + { + "name": "group_key", + "description": "The unique identifier for the group. Required - the call is dropped with a warning if it is missing or empty.", + "isOptional": true, + "type": "str" + }, + { + "name": "properties", + "description": "A dictionary of properties to set on the group.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "timestamp", + "description": "The timestamp of the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC.", + "isOptional": false, + "type": "datetime" + }, + { + "name": "uuid", + "description": "A unique identifier for the event. If provided, it must be a valid UUID string or uuid.UUID instance; invalid values are ignored and replaced with a newly generated UUID.", + "isOptional": false, + "type": "str" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this event.", + "isOptional": true, + "type": "bool" + }, + { + "name": "distinct_id", + "description": "The distinct ID of the user performing the action.", + "isOptional": false, + "type": "Number" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.group_identify('company', 'company_id_in_your_db', {\n 'name': 'Awesome Inc.',\n 'employees': 11\n})" + } + ] + }, + { + "id": "identify_context", + "title": "identify_context", + "description": "Identify the current context with a distinct ID.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "distinct_id", + "description": "The distinct ID to associate with the current context and its children.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + } + }, + { + "id": "join", + "title": "join", + "description": "Attempt to process queued events and end the consumer threads. Do not use directly, call `shutdown()` instead. Failed or undrainable events may be dropped and reported through logging or ``on_error``; returning does not guarantee server receipt. Lifecycle cleanup is attempted once, and cleanup failures are logged without retry.", + "details": "", + "category": null, + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.join()" + } + ] + }, + { + "id": "load_feature_flags", + "title": "load_feature_flags", + "description": "Load feature flags for local evaluation.", + "details": "", + "category": "Feature flags", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.load_feature_flags()" + } + ] + }, + { + "id": "new_context", + "title": "new_context", + "description": "Create a new context for managing shared state. Learn more about [contexts](/docs/libraries/python#contexts).", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "fresh", + "description": "Whether to create a fresh context that doesn't inherit from parent.", + "isOptional": false, + "type": "bool" + }, + { + "name": "capture_exceptions", + "description": "Whether to automatically capture exceptions in this context. If omitted, defaults to this client's exception autocapture setting.", + "isOptional": true, + "type": "bool" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "with client.new_context():\n client.identify_context('')\n client.capture('event_name')" + } + ] + }, + { + "id": "scoped", + "title": "scoped", + "description": "Decorator that creates a new context for the wrapped function using this client.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "fresh", + "description": "Whether to create a fresh context that doesn't inherit from parent.", + "isOptional": false, + "type": "bool" + }, + { + "name": "capture_exceptions", + "description": "Whether to automatically capture exceptions in this context. If omitted, defaults to this client's exception autocapture setting.", + "isOptional": true, + "type": "bool" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "set", + "title": "set", + "description": "Set properties on a person profile.", + "details": "", + "category": "Identification", + "params": [ + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalSetArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Set with distinct id", + "code": "# Set with distinct id\nposthog.set(distinct_id='user123', properties={'name': 'Max Hedgehog'})" + } + ] + }, + { + "id": "set_context_device_id", + "title": "set_context_device_id", + "description": "Set the device ID for the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "device_id", + "description": "The device ID to associate with the current context and its children.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + } + }, + { + "id": "set_context_session", + "title": "set_context_session", + "description": "Set the session ID for the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "session_id", + "description": "The session ID to associate with the current context and its children.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + } + }, + { + "id": "set_once", + "title": "set_once", + "description": "Set properties on a person profile only if they haven't been set before.", + "details": "", + "category": "Identification", + "params": [ + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalSetArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.set_once(distinct_id='user123', properties={'initial_signup_date': '2024-01-01'})" + } + ] + }, + { + "id": "shutdown", + "title": "shutdown", + "description": "Attempt final delivery and permanently shut down the client. Call this for final process cleanup or when disposing of a client that will not be reused. It closes capture lanes, stops background workers and the feature flag poller, and tears down integrations. Do not call it at the end of each request or serverless invocation when the client is shared; call ``flush()`` before returning instead. A shut-down client cannot be reused; create a new client if needed. Normally this method blocks until queued events have been attempted and cleanup finishes. Failed or undrainable events may be dropped and reported through logging or ``on_error``; returning does not guarantee server receipt. Event draining has no timeout. Queued spans get one final flush with up to 30 seconds waiting for an in-flight flush, then a fresh 30-second drain budget; the last HTTP request is not cut short. Any spans it cannot send are discarded with a warning, as are spans still open. Lifecycle cleanup is attempted once, and cleanup failures are logged without retry. When called directly from an SDK callback such as ``on_error``, shutdown is deferred to avoid blocking the worker that invoked the callback. If the callback must coordinate a blocking shutdown, have it signal an application-owned thread and return before that thread calls shutdown. Do not wait inside the callback for another thread or task that calls a lifecycle method.", + "details": "", + "category": null, + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.shutdown()" + } + ] + }, + { + "id": "start_span", + "title": "start_span", + "description": "Start a span for distributed tracing. Alpha. Returns a span handle. Use it as a context manager to make it the active span for the block and end it on exit (recording a raised exception on the way out); or call ``end()`` yourself for a span that cannot wrap a block. Spans started inside the block nest under it automatically. Always returns a usable handle, even when tracing is off, so calling code never branches.", + "details": "", + "category": "Tracing", + "params": [ + { + "name": "name", + "description": "A low-cardinality operation name, e.g. ``GET /users/:id``. Variable values belong in attributes, not the name.", + "isOptional": true, + "type": "str" + }, + { + "name": "kind", + "description": "``internal`` (default), ``server``, ``client``, ``producer`` or ``consumer``.", + "isOptional": true, + "type": "str" + }, + { + "name": "attributes", + "description": "Initial attributes.", + "isOptional": true, + "type": "Mapping[str, Any]" + }, + { + "name": "parent", + "description": "A span handle, or an inbound W3C ``traceparent`` header value to continue a remote trace. Defaults to the active span. A forked child starts with no active span; pass the parent span to continue a trace across a fork.", + "isOptional": false, + "type": "Span" + }, + { + "name": "tracestate", + "description": "The inbound ``tracestate`` header accompanying a ``traceparent`` string ``parent``; preserved and propagated.", + "isOptional": true, + "type": "str" + }, + { + "name": "start_time", + "description": "A ``datetime`` or epoch seconds, to backdate the span.", + "isOptional": false, + "type": "datetime" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Span" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog = Posthog(\"\", traces={\"service_name\": \"checkout-api\"})\n\nwith posthog.start_span(\"POST /checkout\", parent=request.headers.get(\"traceparent\")) as span:\n span.set_attribute(\"plan\", user.plan)\n with posthog.start_span(\"db.query\", kind=\"client\"):\n ...\n outgoing_headers = {\"traceparent\": span.traceparent()}" + } + ] + }, + { + "id": "tag", + "title": "tag", + "description": "Add a tag to the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "name", + "description": "The tag key.", + "isOptional": true, + "type": "str" + }, + { + "name": "value", + "description": "The tag value.", + "isOptional": true, + "type": "Any" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + } + } + ] + }, + { + "id": "PostHogModule", + "title": "PostHog Module Functions", + "description": "Global functions available in the PostHog module", + "functions": [ + { + "id": "alias", + "title": "alias", + "description": "Associate user behaviour before and after they e.g. register, login, or perform some other identifying action.", + "details": "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?\". Particularly useful for associating user behaviour before and after they e.g. register, login, or perform some other identifying action.", + "category": "Identification", + "params": [ + { + "name": "previous_id", + "description": "The unique ID of the user before", + "isOptional": true, + "type": "Number" + }, + { + "name": "distinct_id", + "description": "The current unique id", + "isOptional": true, + "type": "str" + }, + { + "name": "timestamp", + "description": "Optional timestamp for the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC.", + "isOptional": false, + "type": "datetime" + }, + { + "name": "uuid", + "description": "Optional UUID for the event", + "isOptional": true, + "type": "str" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup", + "isOptional": true, + "type": "bool" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Alias user", + "code": "# Alias user\nfrom posthog import alias\nalias(previous_id='distinct_id', distinct_id='alias_id')" + } + ] + }, + { + "id": "capture", + "title": "capture", + "description": "Capture anything a user does within your system.", + "details": "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. A capture call requires an event name to specify the event. We recommend using [verb] [noun], like `movie played` or `movie updated` to easily identify what your events mean later on. Capture takes a number of optional arguments, which are defined by the `OptionalCaptureArgs` type.", + "category": "Events", + "params": [ + { + "name": "event", + "description": "The event name to specify the event **kwargs: Optional arguments including:", + "isOptional": true, + "type": "str" + }, + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalCaptureArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Context and capture usage", + "code": "# Context and capture usage\nfrom posthog import new_context, identify_context, tag_context, capture\n# Enter a new context (e.g. a request/response cycle, an instance of a background job, etc)\nwith new_context():\n # Associate this context with some user, by distinct_id\n identify_context('some user')\n\n # Capture an event, associated with the context-level distinct ID ('some user')\n capture('movie started')\n\n # Capture an event associated with some other user (overriding the context-level distinct ID)\n capture('movie joined', distinct_id='some-other-user')\n\n # Capture an event with some properties\n capture('movie played', properties={'movie_id': '123', 'category': 'romcom'})\n\n # Capture an event with some properties\n capture('purchase', properties={'product_id': '123', 'category': 'romcom'})\n # Capture an event with some associated group\n capture('purchase', groups={'company': 'id:5'})\n\n # Adding a tag to the current context will cause it to appear on all subsequent events\n tag_context('some-tag', 'some-value')\n\n capture('another-event') # Will be captured with `'some-tag': 'some-value'` in the properties dict" + }, + { + "id": "example_2", + "name": "Set event properties", + "code": "# Set event properties\nfrom posthog import capture\ncapture(\n \"user_signed_up\",\n distinct_id=\"distinct_id_of_the_user\",\n properties={\n \"login_type\": \"email\",\n \"is_free_trial\": \"true\"\n }\n)" + } + ] + }, + { + "id": "capture_ai", + "title": "capture_ai", + "description": "Capture an AI event on the dedicated AI capture endpoint. Beta: the signature is stable; operational limits (per-event size cap, batching, endpoint) may change without notice. Takes the same arguments and returns the same value as `capture()`: the event UUID, or None when the event was not admitted (disabled client, or dropped by `before_send`). The event is delivered on an isolated queue with its own consumer pool and a higher per-event size cap, posting to the dedicated AI ingestion endpoint. The payload is sent as given \u2014 no redaction or truncation is applied here.", + "details": "", + "category": "Events", + "params": [ + { + "name": "event", + "description": "The event name, normally one of the `$ai_*` event names. **kwargs: Same optional arguments as `capture()`.", + "isOptional": true, + "type": "str" + }, + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalCaptureArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import capture_ai\n\nuuid = capture_ai(\n \"$ai_generation\",\n distinct_id=\"user_123\",\n properties={\"$ai_model\": \"gpt-5\"},\n)" + } + ] + }, + { + "id": "capture_exception", + "title": "capture_exception", + "description": "Capture exceptions that happen in your code.", + "details": "Capture exception is idempotent - if it is called twice with the same exception instance, only a occurrence will be tracked in posthog. This is because, generally, contexts will cause exceptions to be captured automatically. However, to ensure you track an exception, if you catch and do not re-raise it, capturing it manually is recommended, unless you are certain it will have crossed a context boundary (e.g. by existing a `with posthog.new_context():` block already). If the passed exception was raised and caught, the captured stack trace will consist of every frame between where the exception was raised and the point at which it is captured (the \"traceback\"). If the passed exception was never raised, e.g. if you call `posthog.capture_exception(ValueError(\"Some Error\"))`, the stack trace captured will be the full stack trace at the moment the exception was captured. Note that heavy use of contexts will lead to truncated stack traces, as the exception will be captured by the context entered most recently, 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. `capture_exception` takes the same set of optional arguments as `capture`.", + "category": "Events", + "params": [ + { + "name": "exception", + "description": "The exception to capture. If not provided, the current exception is captured via `sys.exc_info()` **kwargs: Optional capture arguments including distinct_id, properties, timestamp, uuid, groups, flags, send_feature_flags, and disable_geoip. Overriding reserved exception properties through ``properties`` is deprecated and will stop working in the next major version.", + "isOptional": false, + "type": "BaseException" + }, + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalCaptureArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Capture exception", + "code": "# Capture exception\nfrom posthog import capture_exception\ntry:\n risky_operation()\nexcept Exception as e:\n capture_exception(e)" + } + ] + }, + { + "id": "evaluate_flags", + "title": "evaluate_flags", + "description": "Evaluate all feature flags for a user in a single call and return a :class:`FeatureFlagEvaluations` snapshot. Branch on ``.is_enabled()`` / ``.get_flag()`` and pass the same snapshot to ``capture()`` via the ``flags`` option so events carry the exact flag values the code branched on. Prefer this over repeated ``get_feature_flag()`` calls and over ``capture(send_feature_flags=True)`` \u2014 it consolidates flag evaluation into a single ``/flags`` request per incoming request.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The user's distinct ID. If ``None``, falls back to the context distinct_id. If still unresolvable, returns an empty snapshot.", + "isOptional": false, + "type": "Number" + }, + { + "name": "groups", + "description": "Mapping of group type to group key.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "Person properties to use for evaluation.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "Group properties keyed by group type.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "If ``True``, never fall back to remote evaluation and omit flags that cannot be evaluated locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup.", + "isOptional": true, + "type": "bool" + }, + { + "name": "flag_keys", + "description": "Optional list that scopes local evaluation, the underlying ``/flags`` request, and the returned snapshot. When omitted or ``None``, all flags are evaluated. An empty list returns an empty snapshot without evaluating flags. A requested key absent from loaded local definitions is included in one remote fallback per ``evaluate_flags`` call unless ``only_evaluate_locally`` is ``True``. If the server also does not know the key, it is omitted from the snapshot.", + "isOptional": true, + "type": "list[str]" + }, + { + "name": "device_id", + "description": "Optional device ID override. If not provided, falls back to the context device_id (which may be set via tracing headers). Used by experience-continuity flags to match users across distinct_id changes.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "FeatureFlagEvaluations" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import evaluate_flags, capture\nflags = evaluate_flags(\"user_123\", person_properties={\"plan\": \"enterprise\"})\nif flags.is_enabled(\"new-dashboard\"):\n render_new_dashboard()\ncapture(\"page_viewed\", distinct_id=\"user_123\", flags=flags)" + } + ] + }, + { + "id": "feature_enabled", + "title": "feature_enabled", + "description": "Use feature flags to enable or disable features for users.", + "details": "You can call `posthog.load_feature_flags()` before to make sure you're not doing unexpected requests.", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "The feature flag key", + "isOptional": true, + "type": "str" + }, + { + "name": "distinct_id", + "description": "The user's distinct ID", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "Groups mapping", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "Person properties", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "Group properties", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to evaluate only locally", + "isOptional": false, + "type": "bool" + }, + { + "name": "send_feature_flag_events", + "description": "Whether to send feature flag events", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "Optional device ID override for experience-continuity flags", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[bool]" + }, + "examples": [ + { + "id": "example_1", + "name": "Boolean feature flag", + "code": "# Boolean feature flag\nfrom posthog import feature_enabled, get_feature_flag_payload\nis_my_flag_enabled = feature_enabled('flag-key', 'distinct_id_of_your_user')\nif is_my_flag_enabled:\n matched_flag_payload = get_feature_flag_payload('flag-key', 'distinct_id_of_your_user')" + } + ] + }, + { + "id": "feature_flag_definitions", + "title": "feature_flag_definitions", + "description": "Returns loaded feature flags.", + "details": "Returns loaded feature flags, if any. Helpful for debugging what flag information you have loaded.", + "category": "Feature flags", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import feature_flag_definitions\ndefinitions = feature_flag_definitions()" + } + ] + }, + { + "id": "flush", + "title": "flush", + "description": "Attempt immediate delivery of queued events and ended spans, keeping the global client reusable. Call ``flush()`` before a short-lived request or serverless invocation returns when using the shared module-level client. Use ``shutdown()`` only for final process cleanup or a client that will not be reused. Flush does not stop the background workers or feature flag poller, and does not end open spans. Returning does not guarantee server receipt: pending work can remain after a timeout or span export failure, and failed events may be dropped by consumers. Calls made directly from SDK callbacks such as ``on_error`` are deferred rather than blocking the worker that invoked the callback.", + "details": "", + "category": "Client management", + "params": [ + { + "name": "timeout_seconds", + "description": "Seconds to wait for event queues to drain, shared across event lanes. Defaults to 10 seconds. Pass ``None`` to wait without a time limit; this does not guarantee successful delivery. Queued spans flush concurrently, waiting up to this many seconds for an in-flight flush before starting a fresh drain budget. A lock wait timeout leaves the in-flight flush to finish. At least one span request is attempted after acquiring the lock, even with no budget left; retriable failures are retried after backoff while budget remains, with one last attempt at the deadline. The last span request is not cut short and is bounded by the client's ``timeout`` in seconds, so this is not a strict wall-clock limit for the whole call. With ``None``, a retriable span failure is left for a later flush rather than retried within this call.", + "isOptional": true, + "type": "float" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "import posthog\nposthog.capture('event_name', distinct_id='user_id')\n# Before a serverless invocation returns; keep the global client open.\nposthog.flush(timeout_seconds=10)" + } + ] + }, + { + "id": "get_active_span", + "title": "get_active_span", + "description": "The span that is active in the current context, or ``None``. Alpha. Only entering a span (``with posthog.start_span(...) as span:``) makes it active; a span started manually is not. Use it to propagate the trace to the next service: ``span.traceparent()`` is the header value.", + "details": "", + "category": "Tracing", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[Span]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "span = posthog.get_active_span()\nif span is not None:\n headers[\"traceparent\"] = span.traceparent()" + } + ] + }, + { + "id": "get_all_flags", + "title": "get_all_flags", + "description": "Get all flags for a given user.", + "details": "Flags are key-value pairs where the key is the flag key and the value is the flag variant, or True, or False.", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The user's distinct ID", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "Groups mapping", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "Person properties", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "Group properties", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to evaluate only locally", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "Optional device ID override for experience-continuity flags", + "isOptional": true, + "type": "str" + }, + { + "name": "flag_keys_to_evaluate", + "description": "Optional list of flag keys to evaluate (evaluates all if None)", + "isOptional": true, + "type": "list[str]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[dict[str, Union[bool, str]]]" + }, + "examples": [ + { + "id": "example_1", + "name": "All flags for user", + "code": "# All flags for user\nfrom posthog import get_all_flags\nget_all_flags('distinct_id_of_your_user')" + } + ] + }, + { + "id": "get_all_flags_and_payloads", + "title": "get_all_flags_and_payloads", + "description": "Get all feature flag values and payloads for a user.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The user's distinct ID.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "Mapping of group type to group key.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "Person properties to use for evaluation.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "Group properties keyed by group type.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to evaluate only locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup.", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "Optional device ID override for experience-continuity flags.", + "isOptional": true, + "type": "str" + }, + { + "name": "flag_keys_to_evaluate", + "description": "Optional list of flag keys to evaluate. Evaluates all flags when omitted.", + "isOptional": true, + "type": "list[str]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "FlagsAndPayloads" + } + }, + { + "id": "get_feature_flag", + "title": "get_feature_flag", + "description": "Get feature flag variant for users. Used with experiments.", + "details": "`groups` are a mapping from group type to group key. So, if you have a group type of \"organization\" and a group key of \"5\", you would pass groups={\"organization\": \"5\"}. `group_properties` take the format: { group_type_name: { group_properties } }. So, for example, if you have the group type \"organization\" and the group key \"5\", with the properties name, and employee count, you'll send these as: group_properties={\"organization\": {\"name\": \"PostHog\", \"employees\": 11}}.", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "The feature flag key", + "isOptional": true, + "type": "str" + }, + { + "name": "distinct_id", + "description": "The user's distinct ID", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "Groups mapping from group type to group key", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "Person properties", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "Group properties in format { group_type_name: { group_properties } }", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to evaluate only locally", + "isOptional": false, + "type": "bool" + }, + { + "name": "send_feature_flag_events", + "description": "Whether to send feature flag events", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "Optional device ID override for experience-continuity flags", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Union[bool, str, any]" + }, + "examples": [ + { + "id": "example_1", + "name": "Multivariate feature flag", + "code": "# Multivariate feature flag\nfrom posthog import get_feature_flag, get_feature_flag_payload\nenabled_variant = get_feature_flag('flag-key', 'distinct_id_of_your_user')\nif enabled_variant == 'variant-key':\n matched_flag_payload = get_feature_flag_payload('flag-key', 'distinct_id_of_your_user')" + } + ] + }, + { + "id": "get_feature_flag_evaluation_runtime", + "title": "get_feature_flag_evaluation_runtime", + "description": "Return where a locally loaded feature flag is meant to be evaluated.", + "details": "Reads the `evaluation_runtime` each flag definition carries, so no extra request is made. Returns `None` when local evaluation has not loaded a definition for this key. A definition that carries no runtime reports `FeatureFlagEvaluationRuntime.ALL`, the default PostHog applies.", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[FeatureFlagEvaluationRuntime]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import FeatureFlagEvaluationRuntime, get_feature_flag_evaluation_runtime\nruntime = get_feature_flag_evaluation_runtime(\"my-flag\")" + } + ] + }, + { + "id": "get_feature_flag_keys_by_evaluation_runtime", + "title": "get_feature_flag_keys_by_evaluation_runtime", + "description": "Return the keys of locally loaded flags that a runtime can evaluate.", + "details": "A flag set to `FeatureFlagEvaluationRuntime.ALL` suits either runtime, so it is returned for `CLIENT` and for `SERVER`. Use this to decide which flags to hand to a browser when a backend serves flags to its own frontend.", + "category": "Feature flags", + "params": [ + { + "name": "evaluation_runtime", + "description": "", + "isOptional": true, + "type": "FeatureFlagEvaluationRuntime" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "list[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import FeatureFlagEvaluationRuntime, get_feature_flag_keys_by_evaluation_runtime\nclient_keys = get_feature_flag_keys_by_evaluation_runtime(FeatureFlagEvaluationRuntime.CLIENT)" + } + ] + }, + { + "id": "get_feature_flag_payload", + "title": "get_feature_flag_payload", + "description": "Get the payload associated with a feature flag value. Deprecated for new code. Prefer ``evaluate_flags()`` and ``flags.get_flag_payload(key)`` so flag evaluation happens once per request.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "The feature flag key.", + "isOptional": true, + "type": "str" + }, + { + "name": "distinct_id", + "description": "The user's distinct ID.", + "isOptional": true, + "type": "Number" + }, + { + "name": "match_value", + "description": "Optional flag value to use when selecting a payload.", + "isOptional": false, + "type": "bool" + }, + { + "name": "groups", + "description": "Mapping of group type to group key.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "Person properties to use for evaluation.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "Group properties keyed by group type.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to evaluate only locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "send_feature_flag_events", + "description": "Whether to send a $feature_flag_called event.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup.", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "Optional device ID override for experience-continuity flags.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[object]" + } + }, + { + "id": "get_feature_flag_result", + "title": "get_feature_flag_result", + "description": "Get a FeatureFlagResult object which contains the flag result and payload. This method evaluates a feature flag and returns a FeatureFlagResult object containing: - enabled: Whether the flag is enabled - variant: The variant value if the flag has variants - payload: The payload associated with the flag (automatically deserialized from JSON) - key: The flag key - reason: Why the flag was enabled/disabled", + "details": "", + "category": null, + "params": [ + { + "name": "key", + "description": "The feature flag key.", + "isOptional": true, + "type": "str" + }, + { + "name": "distinct_id", + "description": "The user's distinct ID.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "Mapping of group type to group key.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "Person properties to use for evaluation.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "Group properties keyed by group type.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to evaluate only locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "send_feature_flag_events", + "description": "Whether to send a $feature_flag_called event.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup.", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "Optional device ID override for experience-continuity flags.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[FeatureFlagResult]" + } + }, + { + "id": "get_remote_config_payload", + "title": "get_remote_config_payload", + "description": "Get the payload for a remote config feature flag.", + "details": "", + "category": null, + "params": [ + { + "name": "key", + "description": "The key of the feature flag", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "get_tags", + "title": "get_tags", + "description": "Get all tags from the current context. Returns: Dict of all tags in the current context", + "details": "", + "category": "Contexts", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "dict[str, Any]" + } + }, + { + "id": "group_identify", + "title": "group_identify", + "description": "Set properties on a group.", + "details": "", + "category": "Identification", + "params": [ + { + "name": "group_type", + "description": "Type of your group. Required - the call is dropped with a warning if it is missing or empty.", + "isOptional": true, + "type": "str" + }, + { + "name": "group_key", + "description": "Unique identifier of the group. Required - the call is dropped with a warning if it is missing or empty.", + "isOptional": true, + "type": "str" + }, + { + "name": "properties", + "description": "Properties to set on the group", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "timestamp", + "description": "Optional timestamp for the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC.", + "isOptional": false, + "type": "datetime" + }, + { + "name": "uuid", + "description": "Optional UUID for the event", + "isOptional": true, + "type": "str" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup", + "isOptional": true, + "type": "bool" + }, + { + "name": "distinct_id", + "description": "Optional distinct ID of the user performing the action", + "isOptional": false, + "type": "Number" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Group identify", + "code": "# Group identify\nfrom posthog import group_identify\ngroup_identify('company', 'company_id_in_your_db', {\n 'name': 'Awesome Inc.',\n 'employees': 11\n})" + } + ] + }, + { + "id": "identify_context", + "title": "identify_context", + "description": "Identify the current context with a distinct ID.", + "details": "", + "category": "Identification", + "params": [ + { + "name": "distinct_id", + "description": "The distinct ID to associate with the current context and its children", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import identify_context\nidentify_context(\"user_123\")" + } + ] + }, + { + "id": "join", + "title": "join", + "description": "Attempt to process queued events and stop the client's background workers. Use `shutdown()` directly in most cases. Failed or undrainable events may be dropped and reported through logging or ``on_error``; returning does not guarantee server receipt. Lifecycle cleanup is attempted once, and cleanup failures are logged without retry.", + "details": "", + "category": "Client management", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import join\njoin()" + } + ] + }, + { + "id": "load_feature_flags", + "title": "load_feature_flags", + "description": "Load feature flag definitions from PostHog.", + "details": "", + "category": "Feature flags", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import load_feature_flags\nload_feature_flags()" + } + ] + }, + { + "id": "new_context", + "title": "new_context", + "description": "Create a new context scope that will be active for the duration of the with block.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "fresh", + "description": "Whether to start with a fresh context (default: False)", + "isOptional": false, + "type": "bool" + }, + { + "name": "capture_exceptions", + "description": "Whether to capture exceptions raised within the context. If omitted, defaults to the relevant client's exception autocapture setting.", + "isOptional": true, + "type": "bool" + }, + { + "name": "client", + "description": "Optional Posthog client instance to use for this context (default: None)", + "isOptional": true, + "type": "Client" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import new_context, tag, capture\nwith new_context():\n tag(\"request_id\", \"123\")\n capture(\"event_name\", properties={\"property\": \"value\"})" + } + ] + }, + { + "id": "scoped", + "title": "scoped", + "description": "Decorator that creates a new context for the function.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "fresh", + "description": "Whether to start with a fresh context (default: False)", + "isOptional": false, + "type": "bool" + }, + { + "name": "capture_exceptions", + "description": "Whether to capture and track exceptions with posthog error tracking. If omitted, defaults to the global exception autocapture setting.", + "isOptional": true, + "type": "bool" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import scoped, tag, capture\n@scoped()\ndef process_payment(payment_id):\n tag(\"payment_id\", payment_id)\n capture(\"payment_started\")" + } + ] + }, + { + "id": "set", + "title": "set", + "description": "Set properties on a user record.", + "details": "This will overwrite previous people property values. Generally operates similar to `capture`, with distinct_id being an optional argument, defaulting to the current context's distinct ID. If there is no context-level distinct ID, and no override distinct_id is passed, this function will do nothing. Context tags are folded into $set properties, so tagging the current context and then calling `set` will cause those tags to be set on the user (unlike capture, which causes them to just be set on the event).", + "category": "Identification", + "params": [ + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalSetArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Set person properties", + "code": "# Set person properties\nfrom posthog import set\nset(distinct_id='distinct_id', properties={'name': 'Max Hedgehog'})" + } + ] + }, + { + "id": "set_capture_exception_code_variables_context", + "title": "set_capture_exception_code_variables_context", + "description": "Override code-variable capture for exceptions in the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "enabled", + "description": "Whether exceptions captured in this context should include local variable values from stack frames.", + "isOptional": true, + "type": "bool" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "set_code_variables_detect_secrets_context", + "title": "set_code_variables_detect_secrets_context", + "description": "Whether to apply entropy-based secret detection as a last-resort redaction of high-entropy values (API keys, tokens, strong passwords) in captured code variables for the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "enabled", + "description": "", + "isOptional": true, + "type": "bool" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "set_code_variables_ignore_patterns_context", + "title": "set_code_variables_ignore_patterns_context", + "description": "Override code-variable ignore patterns for exceptions in the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "ignore_patterns", + "description": "Variable-name patterns that should be omitted entirely when code variables are captured.", + "isOptional": true, + "type": "list[str]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "set_code_variables_mask_patterns_context", + "title": "set_code_variables_mask_patterns_context", + "description": "Override code-variable mask patterns for exceptions in the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "mask_patterns", + "description": "Variable-name patterns whose values should be replaced with ``***`` when code variables are captured.", + "isOptional": true, + "type": "list[str]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "set_code_variables_mask_url_credentials_context", + "title": "set_code_variables_mask_url_credentials_context", + "description": "Whether to scrub credentials embedded in URLs/DSNs (e.g. user:pass@host) from captured code variables for the current context.", + "details": "", + "category": null, + "params": [ + { + "name": "enabled", + "description": "", + "isOptional": true, + "type": "bool" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "set_context_device_id", + "title": "set_context_device_id", + "description": "Set the device ID for the current context, associating all feature flag requests in this or child contexts with the given device ID.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "device_id", + "description": "The device ID to associate with the current context and its children", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import set_context_device_id\nset_context_device_id(\"device_123\")" + } + ] + }, + { + "id": "set_context_session", + "title": "set_context_session", + "description": "Set the session ID for the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "session_id", + "description": "The session ID to associate with the current context and its children", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import set_context_session\nset_context_session(\"session_123\")" + } + ] + }, + { + "id": "set_once", + "title": "set_once", + "description": "Set properties on a user record, only if they do not yet exist.", + "details": "This will not overwrite previous people property values, unlike `set`. Otherwise, operates in an identical manner to `set`.", + "category": "Identification", + "params": [ + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalSetArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Set property once", + "code": "# Set property once\nfrom posthog import set_once\nset_once(distinct_id='distinct_id', properties={'initial_url': '/blog'})" + } + ] + }, + { + "id": "setup", + "title": "setup", + "description": "Create or return the global PostHog client configured by module settings. Most applications should either instantiate ``Posthog`` directly or set ``posthog.api_key``/other module settings before calling top-level helpers. ``setup()`` is called automatically by global APIs such as ``capture()``. Returns: The global ``Client`` instance. If both ``api_key`` and ``project_api_key`` are missing or blank, the client is disabled and module-level calls become no-ops.", + "details": "", + "category": "Initialization", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Client" + } + }, + { + "id": "shutdown", + "title": "shutdown", + "description": "Attempt final delivery and permanently shut down the global client. Call this for final process cleanup, not at the end of each request or serverless invocation that shares the module-level client. Use ``flush()`` before returning from those invocations instead. Shutdown closes capture lanes, stops background workers and the feature flag poller, and tears down integrations. The global client is not recreated automatically after shutdown. This normally blocks until queued events have been attempted and cleanup finishes. Failed or undrainable events may be dropped and reported through logging or ``on_error``; returning does not guarantee server receipt. Event draining has no timeout. Queued spans get a final flush with up to 30 seconds waiting for an in-flight flush, then a fresh 30-second drain budget; the last HTTP request is not cut short. Unsent queued spans and still-open spans are discarded with a warning. Lifecycle cleanup is attempted once, and cleanup failures are logged without retry. Calls made directly from SDK callbacks such as ``on_error`` are deferred to avoid deadlocking the worker. If blocking completion is required, signal an application-owned thread, return from the callback, and call ``shutdown()`` from that thread. Do not wait inside a callback for another thread or task calling a lifecycle method.", + "details": "", + "category": "Client management", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import shutdown\nshutdown()" + } + ] + }, + { + "id": "start_span", + "title": "start_span", + "description": "Start a span for distributed tracing. Alpha. Returns a span handle. Use it as a context manager to make it the active span for the block and end it on exit (recording a raised exception on the way out); or call ``end()`` yourself for a span that cannot wrap a block. Spans started inside the block nest under it automatically. Always returns a usable handle, even when tracing is off, so calling code never branches.", + "details": "", + "category": "Tracing", + "params": [ + { + "name": "name", + "description": "A low-cardinality operation name, e.g. ``GET /users/:id``. Variable values belong in attributes, not the name.", + "isOptional": true, + "type": "str" + }, + { + "name": "kind", + "description": "``internal`` (default), ``server``, ``client``, ``producer`` or ``consumer``.", + "isOptional": true, + "type": "str" + }, + { + "name": "attributes", + "description": "Initial attributes.", + "isOptional": true, + "type": "Mapping[str, Any]" + }, + { + "name": "parent", + "description": "A span handle, or an inbound W3C ``traceparent`` header value to continue a remote trace. Defaults to the active span.", + "isOptional": false, + "type": "Span" + }, + { + "name": "tracestate", + "description": "The inbound ``tracestate`` header accompanying a ``traceparent`` string ``parent``; preserved and propagated.", + "isOptional": true, + "type": "str" + }, + { + "name": "start_time", + "description": "A ``datetime`` or epoch seconds, to backdate the span.", + "isOptional": false, + "type": "datetime" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Span" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "import posthog\nposthog.traces = {\"service_name\": \"checkout-api\"}\n\nwith posthog.start_span(\"POST /checkout\", parent=request.headers.get(\"traceparent\")) as span:\n span.set_attribute(\"plan\", user.plan)\n with posthog.start_span(\"db.query\", kind=\"client\"):\n ...\n outgoing_headers = {\"traceparent\": span.traceparent()}" + } + ] + }, + { + "id": "tag", + "title": "tag", + "description": "Add a tag to the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "name", + "description": "The tag key", + "isOptional": true, + "type": "str" + }, + { + "name": "value", + "description": "The tag value", + "isOptional": true, + "type": "Any" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import tag\ntag(\"user_id\", \"123\")" + } + ] + } + ] + } + ], + "categories": [ + "Initialization", + "Identification", + "Capture", + "Error Tracking", + "Feature flags", + "Tracing", + "Contexts", + "Events", + "Client management" + ] +} \ No newline at end of file diff --git a/references/posthog-python-references-latest.json b/references/posthog-python-references-latest.json index 9df82cc5..966c6f29 100644 --- a/references/posthog-python-references-latest.json +++ b/references/posthog-python-references-latest.json @@ -2,7 +2,7 @@ "id": "posthog-python", "hogRef": "0.3", "info": { - "version": "7.76.0", + "version": "7.76.1", "id": "posthog-python", "title": "PostHog Python SDK", "description": "Integrate PostHog into any python application.", From cec45622150020731917bbbaaa237a7053c5f9f5 Mon Sep 17 00:00:00 2001 From: Georgis Andonis <6430745+gesh@users.noreply.github.com> Date: Wed, 28 Oct 2026 12:33:42 +0200 Subject: [PATCH 28/60] feat(mcp): add a send_initialize_event option (#1115) * feat(mcp): add a capture_initialize option MCPAnalyticsOptions(capture_initialize=False) stops $mcp_initialize. The option is on by default. Many clients reconnect before every few tool calls, so the handshake event can outnumber $mcp_tool_call. With the option off, record_initialize and the first-request fallback return early. Nothing else changes: the middleware still mints the session token, so tool calls keep their sessions and client names. Co-Authored-By: Claude Opus 5.5 * fix(mcp): keep the handshake identity with capture_initialize off record_initialize returned before handle_identify when the option was off. An identify callback that resolves the user only from the initialize request then left the session anonymous, so later tool calls lost the user. Identify now runs first, and the option stops only the event. Co-Authored-By: Claude Opus 5.5 * refactor(mcp): rename capture_initialize to send_initialize_event PostHogMCP already has a capture_initialize() method that sends the event by hand. An option with the same name next to that method makes readers link the two. The new name says what the option controls. Co-Authored-By: Claude Opus 5.5 * docs(mcp): name the PostHog screens that read $mcp_initialize MCP onboarding and the users and sessions tiles of the default MCP dashboard read $mcp_initialize. The README now says that they show no data while send_initialize_event is off. Co-Authored-By: Claude Opus 5.5 --------- Co-authored-by: Claude Opus 5.5 (cherry picked from commit d8a1b976d665d9f7b3249ac788ef253bb21eed50) --- .../mcp-capture-initialize-option.md | 5 ++ posthog/mcp/README.md | 14 +++- posthog/mcp/_instrumentation.py | 11 ++- posthog/mcp/types.py | 3 + posthog/test/mcp/test_initialize_handshake.py | 74 +++++++++++++++++++ references/public_api_snapshot.txt | 3 +- 6 files changed, 105 insertions(+), 5 deletions(-) create mode 100644 .sampo/changesets/mcp-capture-initialize-option.md diff --git a/.sampo/changesets/mcp-capture-initialize-option.md b/.sampo/changesets/mcp-capture-initialize-option.md new file mode 100644 index 00000000..68efa0a0 --- /dev/null +++ b/.sampo/changesets/mcp-capture-initialize-option.md @@ -0,0 +1,5 @@ +--- +pypi/posthog: minor +--- + +Add a `send_initialize_event` option to MCP analytics. It is on by default. Set it to `False` to stop sending `$mcp_initialize`. diff --git a/posthog/mcp/README.md b/posthog/mcp/README.md index 48b1f281..fd5cc8bf 100644 --- a/posthog/mcp/README.md +++ b/posthog/mcp/README.md @@ -40,8 +40,8 @@ Filter MCP traffic by its `$mcp_*` events and properties instead. A valid echoed conversation handle takes precedence over transport sessions. A request that already carries a transport session or a PostHog session token does not mint a new handle or append a prompt-back block. Requests carrying neither use the conversation fallback. -Intent, model capture, conversation correlation, and MCP exception capture are on by default. -Missing-capability reporting and feedback collection remain off. +Intent, model capture, conversation correlation, MCP exception capture, and `$mcp_initialize` +are on by default. Missing-capability reporting and feedback collection remain off. ```python from posthog.mcp import MCPAnalyticsOptions, instrument @@ -87,6 +87,16 @@ The MCP SDK answers `initialize` itself, so the SDK observes it from outside: - **MCP SDK 1.x over stdio**: from the session's first request. A stdio server serves one client, so this misses only a client that never sends a request. +Set `send_initialize_event=False` to stop sending `$mcp_initialize`. Many clients reconnect before +every few tool calls, so this event can outnumber `$mcp_tool_call`. Nothing else changes in what +the SDK sends: tool calls keep their sessions and client names. In PostHog, MCP onboarding and the +users and sessions tiles of the default MCP dashboard read `$mcp_initialize`. They show no data +while the option is off. + +```python +instrument(server, posthog, MCPAnalyticsOptions(send_initialize_event=False)) +``` + `identify` and `event_properties` run for the handshake too. On MCP SDK 1.x the SDK has no request context for it, so `extra["ctx"]` carries only `.request`. `get_request_headers(extra)` works the same as for any other request. diff --git a/posthog/mcp/_instrumentation.py b/posthog/mcp/_instrumentation.py index 4e72e1de..37877380 100644 --- a/posthog/mcp/_instrumentation.py +++ b/posthog/mcp/_instrumentation.py @@ -260,14 +260,19 @@ async def record_initialize( stateless fleet may never see this client again. The capture itself runs after, in the background, as for every other event.""" + request = {"method": "initialize", "params": params} + await handle_identify(data, session_id, request, extra) + # The option stops only the event. Identify still runs, so later events of + # the session keep an identity that only the handshake can resolve. + if not data.options.send_initialize_event: + return + def text(value: Any) -> Optional[str]: return value if isinstance(value, str) and value else None client_info = params.get("clientInfo") client_info = client_info if isinstance(client_info, dict) else {} agreed = result.get("protocolVersion") if isinstance(result, dict) else None - request = {"method": "initialize", "params": params} - await handle_identify(data, session_id, request, extra) data.mark_session_initialized(session_id) event: Dict[str, Any] = { "event_type": MCPAnalyticsEventType.MCP_INITIALIZE, @@ -321,6 +326,8 @@ async def _maybe_emit_initialize( """Emit ``$mcp_initialize`` once per session from its first request, where no seam observes the real ``initialize`` (see :func:`_first_request_stands_in_for_initialize`).""" + if not data.options.send_initialize_event: + return if not _first_request_stands_in_for_initialize(data, session_source, extra): return if session_id in data.initialized_sessions: diff --git a/posthog/mcp/types.py b/posthog/mcp/types.py index 2e2ff569..055939d4 100644 --- a/posthog/mcp/types.py +++ b/posthog/mcp/types.py @@ -247,6 +247,9 @@ class MCPAnalyticsOptions: # its input schema to remove only PostHog-owned arguments on a fresh server # instance that did not serve tools/list. resolve_original_tool: Optional[Callable[[str], Any]] = None + # Send `$mcp_initialize` for each `initialize` handshake. On by default. + # False sends no `$mcp_initialize`. Nothing else changes. + send_initialize_event: bool = True @dataclass diff --git a/posthog/test/mcp/test_initialize_handshake.py b/posthog/test/mcp/test_initialize_handshake.py index 022235fa..7d6a41b5 100644 --- a/posthog/test/mcp/test_initialize_handshake.py +++ b/posthog/test/mcp/test_initialize_handshake.py @@ -988,3 +988,77 @@ async def test_a_nested_method_before_the_top_level_initialize_is_counted(kind): assert response.status_code == 200, response.text (init,) = _events(client, "$mcp_initialize") assert init["properties"]["$mcp_client_name"] == "nested-client" + + +# --- send_initialize_event=False ----------------------------------------------------- + + +@pytest.mark.parametrize("kind", KINDS) +async def test_send_initialize_event_off_sends_no_initialize_and_keeps_sessions(kind): + # The multi-pod scenario with the option off: no $mcp_initialize. Nothing else + # changes: the token is still minted, so each connection's tool calls keep its + # session and its client name on every pod. + client = FakeClient() + options = MCPAnalyticsOptions( + enable_conversation_id=False, send_initialize_event=False + ) + sessions = [] + async with fleet(client, pods=2, kind=kind, options=options) as lb: + for _ in range(2): + headers, _ = await handshake(lb) + token = decode_session_id(headers.get("mcp-session-id")) + assert token is not None + sessions.append(token.session_id) + await list_tools(lb, headers) + await call_add(lb, headers, 3) + await call_add(lb, headers, 4) + await _flush() + + assert _events(client, "$mcp_initialize") == [] + calls = _events(client, "$mcp_tool_call") + assert [c["properties"]["$session_id"] for c in calls] == [ + sessions[0], + sessions[0], + sessions[1], + sessions[1], + ] + assert {c["properties"]["$mcp_client_name"] for c in calls} == {"codex-mcp-client"} + + +@pytest.mark.parametrize("kind", KINDS) +async def test_send_initialize_event_off_keeps_the_handshake_identity(kind): + # The option stops only the event. An identify that resolves the user only + # from the handshake still attributes the session's later tool calls. + def identify(request, extra): + if request.get("method") == "initialize": + return UserIdentity(distinct_id="user_from_handshake") + return None + + client = FakeClient() + options = MCPAnalyticsOptions( + enable_conversation_id=False, identify=identify, send_initialize_event=False + ) + async with fleet(client, kind=kind, options=options) as lb: + headers, _ = await handshake(lb) + await call_add(lb, headers) + await _flush() + + assert _events(client, "$mcp_initialize") == [] + (call,) = _events(client, "$mcp_tool_call") + assert call["distinct_id"] == "user_from_handshake" + + +@pytest.mark.skipif(MCP_MAJOR >= 2, reason="the first-request fallback is mcp 1.x") +@pytest.mark.parametrize("capture", [True, False]) +async def test_send_initialize_event_off_also_gates_the_first_request_fallback(capture): + # No HTTP, as on stdio: the first tool call stands in for the handshake. + client = FakeClient() + options = MCPAnalyticsOptions( + enable_conversation_id=False, send_initialize_event=capture + ) + _, _, server = make_pod(client, options=options) + await server._tool_manager.call_tool("add", {"a": 1, "b": 2, "context": "stdio"}) + await _flush() + + assert len(_events(client, "$mcp_tool_call")) == 1 + assert len(_events(client, "$mcp_initialize")) == (1 if capture else 0) diff --git a/references/public_api_snapshot.txt b/references/public_api_snapshot.txt index 6e4b5076..9a6aa497 100644 --- a/references/public_api_snapshot.txt +++ b/references/public_api_snapshot.txt @@ -1048,6 +1048,7 @@ attribute posthog.mcp.types.MCPAnalyticsOptions.missing_capability_tool_name: Op attribute posthog.mcp.types.MCPAnalyticsOptions.report_missing: bool = False attribute posthog.mcp.types.MCPAnalyticsOptions.resolve_input_aliases: Optional[ResolveInputAliasesFn] = None attribute posthog.mcp.types.MCPAnalyticsOptions.resolve_original_tool: Optional[Callable[[str], Any]] = None +attribute posthog.mcp.types.MCPAnalyticsOptions.send_initialize_event: bool = True attribute posthog.mcp.types.MCPAnalyticsOptions.server_build: Optional[str] = None attribute posthog.mcp.types.MCPAnalyticsOptions.should_record_input_key: Optional[ShouldRecordInputKeyFn] = None attribute posthog.mcp.types.PreparedToolCall.args: Optional[JsonRecord] = None @@ -1301,7 +1302,7 @@ class posthog.mcp.types.CollectFeedbackOptions(tool_name: Optional[str] = None, class posthog.mcp.types.FeedbackReport(feedback_type: str = 'other', summary: str = '', sentiment: Optional[str] = None, friction_points: Optional[str] = None, suggested_improvement: Optional[str] = None, details: Optional[str] = None, tool_name: Optional[str] = None, task_completed: Optional[bool] = None, extras: JsonRecord = dict(), raw: JsonRecord = dict()) class posthog.mcp.types.MCPAnalyticsContextOptions(description: Optional[str] = None) class posthog.mcp.types.MCPAnalyticsModelOptions(description: Optional[str] = None) -class posthog.mcp.types.MCPAnalyticsOptions(logger: Optional[LoggerFn] = None, report_missing: bool = False, missing_capability_tool_name: Optional[str] = None, enable_conversation_id: bool = True, enable_exception_autocapture: bool = True, context: Union[bool, MCPAnalyticsContextOptions] = True, identify: Optional[Union[IdentifyFn, UserIdentity]] = None, intent_fallback: Optional[IntentFallbackFn] = None, before_send: Optional[BeforeSendFn] = None, event_properties: Optional[EventPropertiesFn] = None, capture_model: Union[bool, MCPAnalyticsModelOptions] = True, collect_feedback: Union[bool, CollectFeedbackOptions] = False, server_build: Optional[str] = None, should_record_input_key: Optional[ShouldRecordInputKeyFn] = None, resolve_input_aliases: Optional[ResolveInputAliasesFn] = None, resolve_original_tool: Optional[Callable[[str], Any]] = None) +class posthog.mcp.types.MCPAnalyticsOptions(logger: Optional[LoggerFn] = None, report_missing: bool = False, missing_capability_tool_name: Optional[str] = None, enable_conversation_id: bool = True, enable_exception_autocapture: bool = True, context: Union[bool, MCPAnalyticsContextOptions] = True, identify: Optional[Union[IdentifyFn, UserIdentity]] = None, intent_fallback: Optional[IntentFallbackFn] = None, before_send: Optional[BeforeSendFn] = None, event_properties: Optional[EventPropertiesFn] = None, capture_model: Union[bool, MCPAnalyticsModelOptions] = True, collect_feedback: Union[bool, CollectFeedbackOptions] = False, server_build: Optional[str] = None, should_record_input_key: Optional[ShouldRecordInputKeyFn] = None, resolve_input_aliases: Optional[ResolveInputAliasesFn] = None, resolve_original_tool: Optional[Callable[[str], Any]] = None, send_initialize_event: bool = True) class posthog.mcp.types.PreparedToolCall(args: Optional[JsonRecord] = None, intent: Optional[str] = None, intent_source: Optional[str] = None, is_missing_capability: bool = False, llm_model: Optional[str] = None, llm_model_source: Optional[MCPAnalyticsModelSource] = None, is_feedback: bool = False, feedback_report: Optional[FeedbackReport] = None, session_id: Optional[str] = None, conversation_id: Optional[str] = None, _conversation_state: Optional[PreparedConversationState] = None) class posthog.mcp.types.ToolInputOptions(should_record_input_key: Optional[ShouldRecordInputKeyFn] = None, input_aliases: Optional[InputAliasMap] = None) class posthog.mcp.types.UserIdentity(distinct_id: str, properties: Optional[JsonRecord] = None, groups: Optional[Dict[str, str]] = None) From 4881284ea6ed773cde1b6eb4129d1a9a51e07d70 Mon Sep 17 00:00:00 2001 From: "releaser-posthog-python[bot]" <262414804+releaser-posthog-python[bot]@users.noreply.github.com> Date: Wed, 28 Oct 2026 13:23:07 +0000 Subject: [PATCH 29/60] chore: Release v7.77.0 [skip ci] (cherry picked from commit 786db579d383a34fc6b4d21d1332743b5eec17b5) --- .sampo/changesets/mcp-capture-initialize-option.md | 5 ----- openfeature-provider/CHANGELOG.md | 6 ++++++ openfeature-provider/pyproject.toml | 2 +- posthog/CHANGELOG.md | 6 ++++++ posthog/version.py | 2 +- pyproject.toml | 2 +- uv.lock | 4 ++-- 7 files changed, 17 insertions(+), 10 deletions(-) delete mode 100644 .sampo/changesets/mcp-capture-initialize-option.md diff --git a/.sampo/changesets/mcp-capture-initialize-option.md b/.sampo/changesets/mcp-capture-initialize-option.md deleted file mode 100644 index 68efa0a0..00000000 --- a/.sampo/changesets/mcp-capture-initialize-option.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -pypi/posthog: minor ---- - -Add a `send_initialize_event` option to MCP analytics. It is on by default. Set it to `False` to stop sending `$mcp_initialize`. diff --git a/openfeature-provider/CHANGELOG.md b/openfeature-provider/CHANGELOG.md index 6490a4fd..9a8af747 100644 --- a/openfeature-provider/CHANGELOG.md +++ b/openfeature-provider/CHANGELOG.md @@ -4,6 +4,12 @@ All notable changes to `openfeature-provider-posthog` are documented here. This file is maintained by [Sampo](https://github.com/bruits/sampo) from changesets in `.sampo/changesets/` that target `pypi/openfeature-provider-posthog`. +## 0.1.128 — 2026-10-28 + +### Patch changes + +- Updated dependencies: posthog@7.77.0 + ## 0.1.127 — 2026-10-28 ### Patch changes diff --git a/openfeature-provider/pyproject.toml b/openfeature-provider/pyproject.toml index bf8a48f0..35d83d81 100644 --- a/openfeature-provider/pyproject.toml +++ b/openfeature-provider/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "openfeature-provider-posthog" -version = "0.1.127" +version = "0.1.128" description = "Official PostHog provider for the OpenFeature Python SDK." readme = "README.md" authors = [{ name = "PostHog", email = "engineering@posthog.com" }] diff --git a/posthog/CHANGELOG.md b/posthog/CHANGELOG.md index 142ff213..5cf1cd2b 100644 --- a/posthog/CHANGELOG.md +++ b/posthog/CHANGELOG.md @@ -1,5 +1,11 @@ # posthog +## 7.77.0 — 2026-10-28 + +### Minor changes + +- [d8a1b97](https://github.com/posthog/posthog-python/commit/d8a1b976d665d9f7b3249ac788ef253bb21eed50) Add a `send_initialize_event` option to MCP analytics. It is on by default. Set it to `False` to stop sending `$mcp_initialize`. — Thanks @gesh! + ## 7.76.1 — 2026-10-28 ### Patch changes diff --git a/posthog/version.py b/posthog/version.py index 4d481543..7b925de0 100644 --- a/posthog/version.py +++ b/posthog/version.py @@ -1 +1 @@ -VERSION = "7.76.1" +VERSION = "7.77.0" diff --git a/pyproject.toml b/pyproject.toml index 09c9338a..92fc154c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "posthog" -version = "7.76.1" +version = "7.77.0" description = "Integrate PostHog into any python application." authors = [{ name = "PostHog", email = "engineering@posthog.com" }] maintainers = [{ name = "PostHog", email = "engineering@posthog.com" }] diff --git a/uv.lock b/uv.lock index 806452ad..9329c839 100644 --- a/uv.lock +++ b/uv.lock @@ -2656,7 +2656,7 @@ wheels = [ [[package]] name = "openfeature-provider-posthog" -version = "0.1.127" +version = "0.1.128" source = { editable = "openfeature-provider" } dependencies = [ { name = "openfeature-sdk" }, @@ -3045,7 +3045,7 @@ wheels = [ [[package]] name = "posthog" -version = "7.76.1" +version = "7.77.0" source = { editable = "." } dependencies = [ { name = "distro" }, From 5cf2935aa94d709fd80b9c6d243e5daf19606587 Mon Sep 17 00:00:00 2001 From: "releaser-posthog-python[bot]" <262414804+releaser-posthog-python[bot]@users.noreply.github.com> Date: Wed, 28 Oct 2026 13:23:12 +0000 Subject: [PATCH 30/60] Update generated references (cherry picked from commit 0894015a3730c8bbdf1cfc3276da12913bcdac27) --- .../posthog-python-references-7.77.0.json | 3548 +++++++++++++++++ .../posthog-python-references-latest.json | 2 +- 2 files changed, 3549 insertions(+), 1 deletion(-) create mode 100644 references/posthog-python-references-7.77.0.json diff --git a/references/posthog-python-references-7.77.0.json b/references/posthog-python-references-7.77.0.json new file mode 100644 index 00000000..70871253 --- /dev/null +++ b/references/posthog-python-references-7.77.0.json @@ -0,0 +1,3548 @@ +{ + "id": "posthog-python", + "hogRef": "0.3", + "info": { + "version": "7.77.0", + "id": "posthog-python", + "title": "PostHog Python SDK", + "description": "Integrate PostHog into any python application.", + "slugPrefix": "posthog-python", + "specUrl": "https://github.com/PostHog/posthog-python" + }, + "types": [ + { + "id": "FeatureFlag", + "name": "FeatureFlag", + "path": "posthog.types.FeatureFlag", + "properties": [ + { + "name": "key", + "type": "str", + "description": "Field: key" + }, + { + "name": "enabled", + "type": "bool", + "description": "Field: enabled" + }, + { + "name": "variant", + "type": "Optional[str]", + "description": "Field: variant" + }, + { + "name": "reason", + "type": "Optional[FlagReason]", + "description": "Field: reason" + }, + { + "name": "metadata", + "type": "Union[FlagMetadata, LegacyFlagMetadata]", + "description": "Field: metadata" + } + ], + "example": "" + }, + { + "id": "FeatureFlagResult", + "name": "FeatureFlagResult", + "path": "posthog.types.FeatureFlagResult", + "properties": [ + { + "name": "key", + "type": "str", + "description": "Field: key" + }, + { + "name": "enabled", + "type": "bool", + "description": "Field: enabled" + }, + { + "name": "variant", + "type": "Optional[str]", + "description": "Field: variant" + }, + { + "name": "payload", + "type": "Optional[Any]", + "description": "Field: payload" + }, + { + "name": "reason", + "type": "Optional[str]", + "description": "Field: reason" + } + ], + "example": "" + }, + { + "id": "FlagMetadata", + "name": "FlagMetadata", + "path": "posthog.types.FlagMetadata", + "properties": [ + { + "name": "id", + "type": "int", + "description": "Field: id" + }, + { + "name": "payload", + "type": "Optional[str]", + "description": "Field: payload" + }, + { + "name": "version", + "type": "int", + "description": "Field: version" + }, + { + "name": "description", + "type": "str", + "description": "Field: description" + }, + { + "name": "has_experiment", + "type": "Optional[bool]", + "description": "Field: has_experiment" + } + ], + "example": "" + }, + { + "id": "FlagReason", + "name": "FlagReason", + "path": "posthog.types.FlagReason", + "properties": [ + { + "name": "code", + "type": "str", + "description": "Field: code" + }, + { + "name": "condition_index", + "type": "Optional[int]", + "description": "Field: condition_index" + }, + { + "name": "description", + "type": "str", + "description": "Field: description" + } + ], + "example": "" + }, + { + "id": "FlagsAndPayloads", + "name": "FlagsAndPayloads", + "path": "posthog.types.FlagsAndPayloads", + "properties": [ + { + "name": "featureFlags", + "type": "Optional[dict[str, Union[bool, str]]]", + "description": "Field: featureFlags" + }, + { + "name": "featureFlagPayloads", + "type": "Optional[dict[str, Any]]", + "description": "Field: featureFlagPayloads" + } + ], + "example": "" + }, + { + "id": "FlagsResponse", + "name": "FlagsResponse", + "path": "posthog.types.FlagsResponse", + "properties": [ + { + "name": "flags", + "type": "dict[str, FeatureFlag]", + "description": "Field: flags" + }, + { + "name": "errorsWhileComputingFlags", + "type": "bool", + "description": "Field: errorsWhileComputingFlags" + }, + { + "name": "requestId", + "type": "str", + "description": "Field: requestId" + }, + { + "name": "quotaLimit", + "type": "Optional[list[str]]", + "description": "Field: quotaLimit" + }, + { + "name": "evaluatedAt", + "type": "Optional[int]", + "description": "Field: evaluatedAt" + }, + { + "name": "minimalFlagCalledEvents", + "type": "bool", + "description": "Field: minimalFlagCalledEvents" + } + ], + "example": "" + }, + { + "id": "LegacyFlagMetadata", + "name": "LegacyFlagMetadata", + "path": "posthog.types.LegacyFlagMetadata", + "properties": [ + { + "name": "payload", + "type": "Any", + "description": "Field: payload" + } + ], + "example": "" + }, + { + "id": "SendFeatureFlagsOptions", + "name": "SendFeatureFlagsOptions", + "path": "posthog.types.SendFeatureFlagsOptions", + "properties": [ + { + "name": "should_send", + "type": "bool", + "description": "Field: should_send" + }, + { + "name": "only_evaluate_locally", + "type": "Optional[bool]", + "description": "Field: only_evaluate_locally" + }, + { + "name": "person_properties", + "type": "Optional[dict[str, Any]]", + "description": "Field: person_properties" + }, + { + "name": "group_properties", + "type": "Optional[dict[str, dict[str, Any]]]", + "description": "Field: group_properties" + }, + { + "name": "flag_keys_filter", + "type": "Optional[list[str]]", + "description": "Field: flag_keys_filter" + } + ], + "example": "" + }, + { + "id": "OptionalCaptureArgs", + "name": "OptionalCaptureArgs", + "path": "posthog.args.OptionalCaptureArgs", + "properties": [ + { + "name": "distinct_id", + "type": "NotRequired[Union[Number, str, UUID, int, any]]", + "description": "Field: distinct_id" + }, + { + "name": "properties", + "type": "NotRequired[Optional[dict[str, Any]]]", + "description": "Field: properties" + }, + { + "name": "timestamp", + "type": "NotRequired[Union[datetime, str, any]]", + "description": "Field: timestamp" + }, + { + "name": "uuid", + "type": "NotRequired[Union[str, UUID, any]]", + "description": "Field: uuid" + }, + { + "name": "groups", + "type": "NotRequired[Optional[dict[str, str]]]", + "description": "Field: groups" + }, + { + "name": "flags", + "type": "NotRequired[Optional[ForwardRef('FeatureFlagEvaluations')]]", + "description": "Field: flags" + }, + { + "name": "send_feature_flags", + "type": "NotRequired[Union[bool, SendFeatureFlagsOptions, any]]", + "description": "Field: send_feature_flags" + }, + { + "name": "disable_geoip", + "type": "NotRequired[Optional[bool]]", + "description": "Field: disable_geoip" + }, + { + "name": "_property_allowlist", + "type": "NotRequired[Optional[frozenset[str]]]", + "description": "Field: _property_allowlist" + } + ], + "example": "" + }, + { + "id": "OptionalSetArgs", + "name": "OptionalSetArgs", + "path": "posthog.args.OptionalSetArgs", + "properties": [ + { + "name": "distinct_id", + "type": "NotRequired[Union[Number, str, UUID, int, any]]", + "description": "Field: distinct_id" + }, + { + "name": "properties", + "type": "NotRequired[Optional[dict[str, Any]]]", + "description": "Field: properties" + }, + { + "name": "timestamp", + "type": "NotRequired[Union[datetime, str, any]]", + "description": "Field: timestamp" + }, + { + "name": "uuid", + "type": "NotRequired[Union[str, UUID, any]]", + "description": "Field: uuid" + }, + { + "name": "disable_geoip", + "type": "NotRequired[Optional[bool]]", + "description": "Field: disable_geoip" + } + ], + "example": "" + }, + { + "id": "SendFeatureFlagsOptions", + "name": "SendFeatureFlagsOptions", + "path": "posthog.types.SendFeatureFlagsOptions", + "properties": [ + { + "name": "should_send", + "type": "bool", + "description": "Field: should_send" + }, + { + "name": "only_evaluate_locally", + "type": "Optional[bool]", + "description": "Field: only_evaluate_locally" + }, + { + "name": "person_properties", + "type": "Optional[dict[str, Any]]", + "description": "Field: person_properties" + }, + { + "name": "group_properties", + "type": "Optional[dict[str, dict[str, Any]]]", + "description": "Field: group_properties" + }, + { + "name": "flag_keys_filter", + "type": "Optional[list[str]]", + "description": "Field: flag_keys_filter" + } + ], + "example": "" + } + ], + "classes": [ + { + "id": "PostHog", + "title": "PostHog", + "description": "This is the SDK reference for the PostHog Python SDK. You can learn more about example usage in the [Python SDK documentation](/docs/libraries/python). You can also follow [Flask](/docs/libraries/flask) and [Django](/docs/libraries/django) guides to integrate PostHog into your project. For long-running applications, create one client during application startup and reuse it for the lifetime of the process. This keeps background queues predictable and makes shutdown flushing straightforward. Multiple clients are still supported for intentional multi-project or multi-host setups.", + "functions": [ + { + "id": "__init__", + "title": "Client", + "description": "Initialize a new PostHog client instance.", + "details": "", + "category": "Initialization", + "params": [ + { + "name": "project_api_key", + "description": "PostHog project API key/token.", + "isOptional": true, + "type": "str" + }, + { + "name": "host", + "description": "PostHog host. Defaults to the US ingestion endpoint when not set. App hosts such as ``https://us.posthog.com`` are mapped to the corresponding ingestion host.", + "isOptional": false, + "type": "any" + }, + { + "name": "debug", + "description": "Enable verbose SDK logging and re-raise errors from public API methods.", + "isOptional": false, + "type": "bool" + }, + { + "name": "max_queue_size", + "description": "Maximum number of events buffered before upload.", + "isOptional": false, + "type": "int" + }, + { + "name": "send", + "description": "If False, queueing succeeds but events are not sent.", + "isOptional": false, + "type": "bool" + }, + { + "name": "on_error", + "description": "Optional callback invoked by background consumers when an upload fails. Keep it short and non-blocking. Calling lifecycle methods directly is safe and deferred, but do not start another thread or task that calls ``flush()``, ``join()``, or ``shutdown()`` and then wait for it from the callback.", + "isOptional": false, + "type": "any" + }, + { + "name": "flush_at", + "description": "Number of queued events that triggers a batch upload.", + "isOptional": false, + "type": "int" + }, + { + "name": "flush_interval", + "description": "Maximum seconds a background consumer waits before flushing a partial batch.", + "isOptional": false, + "type": "float" + }, + { + "name": "gzip", + "description": "Whether to gzip event upload payloads.", + "isOptional": false, + "type": "bool" + }, + { + "name": "max_retries", + "description": "Number of upload retries. Values below 0 are treated as 0.", + "isOptional": false, + "type": "int" + }, + { + "name": "sync_mode", + "description": "If True, send each event synchronously instead of using background worker threads. This blocks the calling thread; in asyncio applications such as FastAPI, use ``AsyncPosthog`` instead.", + "isOptional": false, + "type": "bool" + }, + { + "name": "timeout", + "description": "HTTP request timeout in seconds for event uploads.", + "isOptional": false, + "type": "float" + }, + { + "name": "thread", + "description": "Number of background consumer threads.", + "isOptional": false, + "type": "int" + }, + { + "name": "poll_interval", + "description": "Seconds between local feature flag definition refreshes.", + "isOptional": false, + "type": "int" + }, + { + "name": "personal_api_key", + "description": "Deprecated alias for ``secret_key``. Still honored for backwards compatibility; prefer ``secret_key``, which also accepts a Project Secret API Key.", + "isOptional": false, + "type": "any" + }, + { + "name": "disabled", + "description": "If True, disable captures and API requests. Useful in tests.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable server-side GeoIP enrichment. Defaults to True.", + "isOptional": false, + "type": "bool" + }, + { + "name": "is_server", + "description": "Whether events are emitted from a server-side runtime. Defaults to True; set to False when using the SDK as a client/CLI so the device OS is attributed to the person normally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "historical_migration", + "description": "Mark events as historical migration imports.", + "isOptional": false, + "type": "bool" + }, + { + "name": "feature_flags_request_timeout_seconds", + "description": "Timeout in seconds for feature flag and remote config requests.", + "isOptional": false, + "type": "float" + }, + { + "name": "feature_flags_request_max_retries", + "description": "Number of retries for feature flag requests after network, transport, or timeout failures. Defaults to 1. Set to 0 to disable retries.", + "isOptional": false, + "type": "int" + }, + { + "name": "super_properties", + "description": "Properties merged into every captured event.", + "isOptional": false, + "type": "any" + }, + { + "name": "enable_exception_autocapture", + "description": "Automatically capture uncaught exceptions.", + "isOptional": false, + "type": "bool" + }, + { + "name": "log_captured_exceptions", + "description": "Also log exceptions captured by error tracking.", + "isOptional": false, + "type": "bool" + }, + { + "name": "project_root", + "description": "Root path used to determine in-app stack frames for captured exceptions. Defaults to the current working directory.", + "isOptional": false, + "type": "any" + }, + { + "name": "privacy_mode", + "description": "For AI observability, capture usage metadata without prompt inputs or outputs.", + "isOptional": false, + "type": "bool" + }, + { + "name": "before_send", + "description": "Optional callback that can modify or drop events before upload. Return ``None`` to drop an event.", + "isOptional": false, + "type": "any" + }, + { + "name": "flag_fallback_cache_url", + "description": "Optional feature flag fallback cache URL, such as ``memory://local/?ttl=300&size=10000`` or a Redis URL.", + "isOptional": false, + "type": "any" + }, + { + "name": "enable_local_evaluation", + "description": "Whether to poll feature flag definitions for local evaluation when a personal API key is configured.", + "isOptional": false, + "type": "bool" + }, + { + "name": "flag_definition_cache_provider", + "description": "Optional external cache provider for sharing feature flag definitions across workers.", + "isOptional": true, + "type": "FlagDefinitionCacheProvider" + }, + { + "name": "capture_exception_code_variables", + "description": "Capture local variable values on exception stack frames.", + "isOptional": false, + "type": "bool" + }, + { + "name": "code_variables_mask_patterns", + "description": "Variable-name patterns to mask when capturing code variables.", + "isOptional": false, + "type": "any" + }, + { + "name": "code_variables_ignore_patterns", + "description": "Variable-name patterns to omit when capturing code variables.", + "isOptional": false, + "type": "any" + }, + { + "name": "code_variables_mask_url_credentials", + "description": "Scrub credentials embedded in URLs/DSNs (e.g. ``user:pass@host``) from captured code variables, regardless of the surrounding variable name. Defaults to True.", + "isOptional": false, + "type": "any" + }, + { + "name": "code_variables_detect_secrets", + "description": "Last-resort entropy-based detection that redacts high-entropy secret-looking values (API keys, tokens, strong passwords) sitting in innocuously-named variables, after the name and URL checks. Skips structured ids (UUIDs, ObjectIds, hashes). Defaults to True.", + "isOptional": false, + "type": "any" + }, + { + "name": "in_app_modules", + "description": "Module/package prefixes treated as in-app frames in captured exceptions.", + "isOptional": false, + "type": "UnionType[list[str], any]" + }, + { + "name": "enable_exception_autocapture_rate_limiting", + "description": "Rate limit autocaptured exceptions client-side with a token bucket per exception type. Disabled by default.", + "isOptional": false, + "type": "bool" + }, + { + "name": "exception_autocapture_bucket_size", + "description": "Maximum burst of autocaptured exceptions allowed per exception type (token bucket size, clamped to 0-100).", + "isOptional": false, + "type": "int" + }, + { + "name": "exception_autocapture_refill_rate", + "description": "Tokens restored per refill interval for each exception type's bucket.", + "isOptional": false, + "type": "int" + }, + { + "name": "exception_autocapture_refill_interval_seconds", + "description": "Seconds between token refills for autocaptured exception rate limiting.", + "isOptional": false, + "type": "int" + }, + { + "name": "capture_mode", + "description": "Capture wire protocol to use. Defaults to ``CaptureMode.V0`` (legacy ``/batch/``). Set ``CaptureMode.V1`` (or pass the string ``\"v1\"``) to opt into ``/i/v1/analytics/events``. When omitted, the ``POSTHOG_CAPTURE_MODE`` env var is consulted, then ``V0``.", + "isOptional": false, + "type": "CaptureMode" + }, + { + "name": "capture_compression", + "description": "Request-body compression for capture-v1 uploads (ignored in V0, which uses ``gzip``). ``CaptureCompression.GZIP`` or ``DEFLATE`` (or the strings ``\"gzip\"``/``\"deflate\"``). When omitted, the ``POSTHOG_CAPTURE_COMPRESSION`` env var is consulted, then the legacy ``gzip`` flag, then no compression.", + "isOptional": false, + "type": "CaptureCompression" + }, + { + "name": "secret_key", + "description": "A Personal API Key or Project Secret API Key, used to authenticate local feature flag evaluation, remote config payloads, and decrypted flag payloads. Example:: posthog.Client(project_api_key, secret_key=\"phx_...\")", + "isOptional": false, + "type": "any" + }, + { + "name": "metrics", + "description": "", + "isOptional": true, + "type": "dict" + }, + { + "name": "enable_full_ai_capture", + "description": "Route PostHog AI wrapper events through the dedicated AI capture endpoint and capture full AI content: skips string truncation and passes media (base64/data URIs) through unredacted. ``privacy_mode`` always wins. Defaults to False.", + "isOptional": false, + "type": "bool" + }, + { + "name": "capture_trace_context", + "description": "When OpenTelemetry is installed and a valid span is active at capture time, add its trace and span IDs as ``$trace_id`` and ``$span_id`` properties to events captured with ``capture()`` and ``capture_ai()``, so they can be correlated with backend traces. Explicit ``$trace_id``/``$span_id`` values passed in ``properties`` win. Exception events (``capture_exception``) always attach these IDs regardless of this setting. Defaults to False.", + "isOptional": false, + "type": "bool" + }, + { + "name": "_use_ai_lane", + "description": "", + "isOptional": false, + "type": "bool" + }, + { + "name": "_enable_multimodal_capture", + "description": "", + "isOptional": false, + "type": "bool" + }, + { + "name": "traces", + "description": "Config dict for distributed tracing: ``service_name``, ``service_version``, ``environment``, ``resource_attributes``, ``flush_interval`` (5 s), ``max_queue_size`` (2048), ``max_export_batch_size`` (512), ``max_live_spans`` (10000), ``max_span_age`` (3600 s), ``max_attributes_per_span`` (128), ``max_events_per_span`` (128), ``max_attribute_value_length`` (8192). ``before_span_send`` is a callable, or a list run in order, that receives each finished span as a dict (``trace_id``, ``span_id`` and ``parent_span_id`` are read-only) and returns it, edited, or ``None`` to drop it; a hook that raises drops the span. Tracing is off until this is provided. Spans export on a background timer even with ``sync_mode``; serverless handlers should call ``flush()`` before returning. Defaults to None.", + "isOptional": true, + "type": "dict" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import Posthog\n\nposthog = Posthog('', host='')" + } + ] + }, + { + "id": "alias", + "title": "alias", + "description": "Create an alias between two distinct IDs.", + "details": "", + "category": "Identification", + "params": [ + { + "name": "previous_id", + "description": "The previous distinct ID. Required - the call is dropped with a warning if it is missing or empty.", + "isOptional": true, + "type": "Number" + }, + { + "name": "distinct_id", + "description": "The new distinct ID to alias to. Falls back to the context distinct ID; the call is dropped with a warning if neither is available.", + "isOptional": true, + "type": "str" + }, + { + "name": "timestamp", + "description": "The timestamp of the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC.", + "isOptional": false, + "type": "datetime" + }, + { + "name": "uuid", + "description": "A unique identifier for the event. If provided, it must be a valid UUID string or uuid.UUID instance; invalid values are ignored and replaced with a newly generated UUID.", + "isOptional": true, + "type": "str" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this event.", + "isOptional": true, + "type": "bool" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.alias(previous_id='distinct_id', distinct_id='alias_id')" + } + ] + }, + { + "id": "capture", + "title": "capture", + "description": "Captures an event manually. [Learn about capture best practices](https://posthog.com/docs/product-analytics/capture-events)", + "details": "", + "category": "Capture", + "params": [ + { + "name": "event", + "description": "The event name to capture.", + "isOptional": true, + "type": "str" + }, + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalCaptureArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Anonymous event", + "code": "# Anonymous event\nposthog.capture('some-anon-event')" + }, + { + "id": "example_2", + "name": "Context usage", + "code": "# Context usage\nfrom posthog import identify_context, new_context\nwith new_context():\n identify_context('distinct_id_of_the_user')\n posthog.capture('user_signed_up')\n posthog.capture('user_logged_in')\n posthog.capture('some-custom-action', distinct_id='distinct_id_of_the_user')" + }, + { + "id": "example_3", + "name": "Set event properties", + "code": "# Set event properties\nposthog.capture(\n \"user_signed_up\",\n distinct_id=\"distinct_id_of_the_user\",\n properties={\n \"login_type\": \"email\",\n \"is_free_trial\": \"true\"\n }\n)" + }, + { + "id": "example_4", + "name": "Page view event", + "code": "# Page view event\nposthog.capture('$pageview', distinct_id=\"distinct_id_of_the_user\", properties={'$current_url': 'https://example.com'})" + } + ] + }, + { + "id": "capture_ai", + "title": "capture_ai", + "description": "Capture an AI event on the dedicated AI capture endpoint. Beta: the signature is stable; operational limits (per-event size cap, batching, endpoint) may change without notice. Takes the same arguments and returns the same value as `capture()`: the event UUID, or None when the event was not admitted (disabled client, or dropped by `before_send`). The event is queued on an isolated AI lane with its own consumer pool and a higher per-event size cap, posting to the dedicated AI ingestion endpoint. The payload is sent as given \u2014 no redaction or truncation is applied here.", + "details": "", + "category": "Capture", + "params": [ + { + "name": "event", + "description": "", + "isOptional": true, + "type": "str" + }, + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalCaptureArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + } + }, + { + "id": "capture_exception", + "title": "capture_exception", + "description": "Capture an exception for error tracking. When OpenTelemetry is installed and a valid span is active, its trace and span IDs are added as ``$trace_id`` and ``$span_id`` event properties.", + "details": "", + "category": "Error Tracking", + "params": [ + { + "name": "exception", + "description": "The exception to capture.", + "isOptional": true, + "type": "BaseException" + }, + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalCaptureArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "try:\n # Some code that might fail\n pass\nexcept Exception as e:\n posthog.capture_exception(e, 'user_distinct_id', properties=additional_properties)" + } + ] + }, + { + "id": "evaluate_flags", + "title": "evaluate_flags", + "description": "Evaluate all feature flags for a user in a single call and return a :class:`FeatureFlagEvaluations` snapshot. Branch on ``.is_enabled()`` / ``.get_flag()`` and pass the same snapshot to :meth:`capture` via the ``flags`` option so events carry the exact flag values the code branched on. Prefer this over repeated ``get_feature_flag()`` calls and over ``capture(send_feature_flags=True)`` \u2014 it consolidates flag evaluation into a single ``/flags`` request per incoming request. Local evaluation is transparent: when the poller resolves a flag, the snapshot's ``$feature_flag_called`` events are tagged ``locally_evaluated=True`` and reason ``\"Evaluated locally\"``.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The user's distinct ID. If ``None``, falls back to the context distinct_id. If still unresolvable, returns an empty snapshot.", + "isOptional": false, + "type": "Number" + }, + { + "name": "groups", + "description": "Mapping of group type to group key.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "Person properties to use for evaluation.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "Group properties keyed by group type.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "If True, never fall back to remote evaluation \u2014 flags that can't be evaluated locally are simply omitted from the snapshot.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup.", + "isOptional": true, + "type": "bool" + }, + { + "name": "flag_keys", + "description": "Optional list that scopes local evaluation, the underlying ``/flags`` request, and the returned snapshot. When omitted or ``None``, all flags are evaluated. An empty list returns an empty snapshot without evaluating flags. A requested key absent from loaded local definitions is included in one remote fallback per ``evaluate_flags`` call unless ``only_evaluate_locally`` is True. If the server also does not know the key, it is omitted from the snapshot.", + "isOptional": true, + "type": "list[str]" + }, + { + "name": "device_id", + "description": "Optional device ID override. If not provided, falls back to the context device_id (which may be set via tracing headers). Used by experience-continuity flags to match users across distinct_id changes.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "FeatureFlagEvaluations" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "flags = posthog.evaluate_flags(\n \"user_123\",\n person_properties={\"plan\": \"enterprise\"},\n)\nif flags.is_enabled(\"new-dashboard\"):\n render_new_dashboard()\nposthog.capture(\"page_viewed\", distinct_id=\"user_123\", flags=flags)" + } + ] + }, + { + "id": "feature_enabled", + "title": "feature_enabled", + "description": "Check if a feature flag is enabled for a user.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "The feature flag key.", + "isOptional": true, + "type": "str" + }, + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to only evaluate locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "send_feature_flag_events", + "description": "Whether to send feature flag events.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[bool]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "is_my_flag_enabled = posthog.feature_enabled('flag-key', 'distinct_id_of_your_user')\nif is_my_flag_enabled:\n # Do something differently for this user\n # Optional: fetch the payload\n matched_flag_payload = posthog.get_feature_flag_payload('flag-key', 'distinct_id_of_your_user')" + } + ] + }, + { + "id": "feature_flag_definitions", + "title": "feature_flag_definitions", + "description": "Return feature flag definitions loaded for local evaluation. Returns: The currently loaded feature flag definitions, or ``None`` before local evaluation has loaded definitions.", + "details": "", + "category": "Feature flags", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "flush", + "title": "flush", + "description": "Attempt immediate delivery of queued events and ended spans, keeping the client reusable. For a client shared across requests or serverless invocations (including the module-level client), call ``flush()`` before a short-lived invocation returns. Use ``shutdown()`` instead only for final process cleanup or when disposing of a client that will not be reused. Flush does not stop the background workers or feature flag poller, and does not end open spans. Returning does not guarantee server receipt: pending work can remain after a timeout or span export failure, and failed events may be dropped by the consumers. Calls made directly from SDK callbacks such as ``on_error`` are deferred rather than blocking the worker that invoked the callback.", + "details": "", + "category": null, + "params": [ + { + "name": "timeout_seconds", + "description": "Seconds to wait for event queues to drain, shared across event lanes. Defaults to 10 seconds. Pass ``None`` to wait without a time limit; this does not guarantee successful delivery. Queued spans flush concurrently. The span exporter waits up to this many seconds for an in-flight flush, then uses a fresh drain budget once it acquires the lock. If the lock wait times out, the in-flight flush is left to finish. At least one span request is attempted after acquiring the lock, even with no budget left; retriable failures are retried after backoff while budget remains, with one last attempt at the deadline. Each span request is bounded by the client's ``timeout`` in seconds, and the last request is not cut short. This is therefore not a strict wall-clock limit for the whole call. With ``None``, a retriable span failure is left for a later flush rather than retried within this call.", + "isOptional": true, + "type": "float" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.capture('event_name', distinct_id='user_id')\n# Before a serverless invocation returns; keep the shared client open.\nposthog.flush(timeout_seconds=10)" + } + ] + }, + { + "id": "get_active_span", + "title": "get_active_span", + "description": "The span that is active in the current context, or ``None``. Alpha. Only entering a span (``with posthog.start_span(...) as span:``) makes it active; a span started manually is not. Use it to propagate the trace to the next service: ``span.traceparent()`` is the header value.", + "details": "", + "category": "Tracing", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[Span]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "span = posthog.get_active_span()\nif span is not None:\n headers[\"traceparent\"] = span.traceparent()" + } + ] + }, + { + "id": "get_all_flags", + "title": "get_all_flags", + "description": "Get all feature flags for a user.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to only evaluate locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "flag_keys_to_evaluate", + "description": "A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance.", + "isOptional": true, + "type": "list[str]" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[dict[str, Union[bool, str]]]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.get_all_flags('distinct_id_of_your_user')" + } + ] + }, + { + "id": "get_all_flags_and_payloads", + "title": "get_all_flags_and_payloads", + "description": "Get all feature flags and their payloads for a user.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to only evaluate locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "flag_keys_to_evaluate", + "description": "A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance.", + "isOptional": true, + "type": "list[str]" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "FlagsAndPayloads" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.get_all_flags_and_payloads('distinct_id_of_your_user')" + } + ] + }, + { + "id": "get_feature_flag", + "title": "get_feature_flag", + "description": "Get multivariate feature flag value for a user.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "The feature flag key.", + "isOptional": true, + "type": "str" + }, + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to only evaluate locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "send_feature_flag_events", + "description": "Whether to send feature flag events.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Union[bool, str, any]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "enabled_variant = posthog.get_feature_flag('flag-key', 'distinct_id_of_your_user')\nif enabled_variant == 'variant-key': # replace 'variant-key' with the key of your variant\n # Do something differently for this user\n # Optional: fetch the payload\n matched_flag_payload = posthog.get_feature_flag_payload('flag-key', 'distinct_id_of_your_user')" + } + ] + }, + { + "id": "get_feature_flag_evaluation_runtime", + "title": "get_feature_flag_evaluation_runtime", + "description": "Return where a locally loaded feature flag is meant to be evaluated.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "The feature flag key.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[FeatureFlagEvaluationRuntime]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import FeatureFlagEvaluationRuntime\n\nruntime = posthog.get_feature_flag_evaluation_runtime(\"my-flag\")\nif runtime is FeatureFlagEvaluationRuntime.SERVER:\n ..." + } + ] + }, + { + "id": "get_feature_flag_keys_by_evaluation_runtime", + "title": "get_feature_flag_keys_by_evaluation_runtime", + "description": "Return the keys of locally loaded flags that a runtime can evaluate. A flag set to ``FeatureFlagEvaluationRuntime.ALL`` suits either runtime, so it is returned for ``CLIENT`` and for ``SERVER``, and asking for ``ALL`` returns every loaded flag. Use this to decide which flags to hand to a browser when a backend serves flags to its own frontend.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "evaluation_runtime", + "description": "The runtime to match, as a ``FeatureFlagEvaluationRuntime`` or its string value.", + "isOptional": true, + "type": "FeatureFlagEvaluationRuntime" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "list[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import FeatureFlagEvaluationRuntime\n\nclient_keys = posthog.get_feature_flag_keys_by_evaluation_runtime(\n FeatureFlagEvaluationRuntime.CLIENT\n)" + } + ] + }, + { + "id": "get_feature_flag_payload", + "title": "get_feature_flag_payload", + "description": "Get the payload for a feature flag.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "The feature flag key.", + "isOptional": true, + "type": "str" + }, + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "match_value", + "description": "The specific flag value to get payload for.", + "isOptional": false, + "type": "bool" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to only evaluate locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "send_feature_flag_events", + "description": "Deprecated. Use get_feature_flag() instead if you need events.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[object]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "is_my_flag_enabled = posthog.feature_enabled('flag-key', 'distinct_id_of_your_user')\n\nif is_my_flag_enabled:\n # Do something differently for this user\n # Optional: fetch the payload\n matched_flag_payload = posthog.get_feature_flag_payload('flag-key', 'distinct_id_of_your_user')" + } + ] + }, + { + "id": "get_feature_flag_result", + "title": "get_feature_flag_result", + "description": "Get a FeatureFlagResult object which contains the flag result and payload for a key by evaluating locally or remotely depending on whether local evaluation is enabled and the flag can be locally evaluated. This also captures the `$feature_flag_called` event unless `send_feature_flag_events` is `False`.", + "details": "", + "category": null, + "params": [ + { + "name": "key", + "description": "The feature flag key.", + "isOptional": true, + "type": "str" + }, + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to only evaluate locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "send_feature_flag_events", + "description": "Whether to send feature flag events.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[FeatureFlagResult]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "flag_result = posthog.get_feature_flag_result('flag-key', 'distinct_id_of_your_user')\nif flag_result and flag_result.get_value() == 'variant-key':\n # Do something differently for this user\n # Optional: fetch the payload\n matched_flag_payload = flag_result.payload" + } + ] + }, + { + "id": "get_feature_flags_and_payloads", + "title": "get_feature_flags_and_payloads", + "description": "Get feature flags and payloads for a user.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "flag_keys_to_evaluate", + "description": "A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance.", + "isOptional": true, + "type": "list[str]" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "FlagsAndPayloads" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "result = posthog.get_feature_flags_and_payloads('')" + } + ] + }, + { + "id": "get_feature_payloads", + "title": "get_feature_payloads", + "description": "Get feature flag payloads for a user, preserving valid serialized JSON.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "flag_keys_to_evaluate", + "description": "A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance.", + "isOptional": true, + "type": "list[str]" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "dict[str, Optional[str]]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "payloads = posthog.get_feature_payloads('')" + } + ] + }, + { + "id": "get_feature_variants", + "title": "get_feature_variants", + "description": "Get feature flag variants for a user.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "flag_keys_to_evaluate", + "description": "A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance.", + "isOptional": true, + "type": "list[str]" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "dict[str, Union[bool, str]]" + } + }, + { + "id": "get_flags_decision", + "title": "get_flags_decision", + "description": "Get feature flags decision.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": false, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "flag_keys_to_evaluate", + "description": "A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance.", + "isOptional": true, + "type": "list[str]" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "FlagsResponse" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "decision = posthog.get_flags_decision('user123')" + } + ] + }, + { + "id": "get_remote_config_payload", + "title": "get_remote_config_payload", + "description": "Get the payload for a remote config feature flag.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "The remote config feature flag key.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "get_tags", + "title": "get_tags", + "description": "Get all tags from the current context. Returns: Dict of all tags in the current context.", + "details": "", + "category": "Contexts", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "dict[str, Any]" + } + }, + { + "id": "group_identify", + "title": "group_identify", + "description": "Identify a group and set its properties.", + "details": "", + "category": "Identification", + "params": [ + { + "name": "group_type", + "description": "The type of group (e.g., 'company', 'team'). Required - the call is dropped with a warning if it is missing or empty.", + "isOptional": true, + "type": "str" + }, + { + "name": "group_key", + "description": "The unique identifier for the group. Required - the call is dropped with a warning if it is missing or empty.", + "isOptional": true, + "type": "str" + }, + { + "name": "properties", + "description": "A dictionary of properties to set on the group.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "timestamp", + "description": "The timestamp of the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC.", + "isOptional": false, + "type": "datetime" + }, + { + "name": "uuid", + "description": "A unique identifier for the event. If provided, it must be a valid UUID string or uuid.UUID instance; invalid values are ignored and replaced with a newly generated UUID.", + "isOptional": false, + "type": "str" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this event.", + "isOptional": true, + "type": "bool" + }, + { + "name": "distinct_id", + "description": "The distinct ID of the user performing the action.", + "isOptional": false, + "type": "Number" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.group_identify('company', 'company_id_in_your_db', {\n 'name': 'Awesome Inc.',\n 'employees': 11\n})" + } + ] + }, + { + "id": "identify_context", + "title": "identify_context", + "description": "Identify the current context with a distinct ID.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "distinct_id", + "description": "The distinct ID to associate with the current context and its children.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + } + }, + { + "id": "join", + "title": "join", + "description": "Attempt to process queued events and end the consumer threads. Do not use directly, call `shutdown()` instead. Failed or undrainable events may be dropped and reported through logging or ``on_error``; returning does not guarantee server receipt. Lifecycle cleanup is attempted once, and cleanup failures are logged without retry.", + "details": "", + "category": null, + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.join()" + } + ] + }, + { + "id": "load_feature_flags", + "title": "load_feature_flags", + "description": "Load feature flags for local evaluation.", + "details": "", + "category": "Feature flags", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.load_feature_flags()" + } + ] + }, + { + "id": "new_context", + "title": "new_context", + "description": "Create a new context for managing shared state. Learn more about [contexts](/docs/libraries/python#contexts).", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "fresh", + "description": "Whether to create a fresh context that doesn't inherit from parent.", + "isOptional": false, + "type": "bool" + }, + { + "name": "capture_exceptions", + "description": "Whether to automatically capture exceptions in this context. If omitted, defaults to this client's exception autocapture setting.", + "isOptional": true, + "type": "bool" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "with client.new_context():\n client.identify_context('')\n client.capture('event_name')" + } + ] + }, + { + "id": "scoped", + "title": "scoped", + "description": "Decorator that creates a new context for the wrapped function using this client.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "fresh", + "description": "Whether to create a fresh context that doesn't inherit from parent.", + "isOptional": false, + "type": "bool" + }, + { + "name": "capture_exceptions", + "description": "Whether to automatically capture exceptions in this context. If omitted, defaults to this client's exception autocapture setting.", + "isOptional": true, + "type": "bool" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "set", + "title": "set", + "description": "Set properties on a person profile.", + "details": "", + "category": "Identification", + "params": [ + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalSetArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Set with distinct id", + "code": "# Set with distinct id\nposthog.set(distinct_id='user123', properties={'name': 'Max Hedgehog'})" + } + ] + }, + { + "id": "set_context_device_id", + "title": "set_context_device_id", + "description": "Set the device ID for the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "device_id", + "description": "The device ID to associate with the current context and its children.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + } + }, + { + "id": "set_context_session", + "title": "set_context_session", + "description": "Set the session ID for the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "session_id", + "description": "The session ID to associate with the current context and its children.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + } + }, + { + "id": "set_once", + "title": "set_once", + "description": "Set properties on a person profile only if they haven't been set before.", + "details": "", + "category": "Identification", + "params": [ + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalSetArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.set_once(distinct_id='user123', properties={'initial_signup_date': '2024-01-01'})" + } + ] + }, + { + "id": "shutdown", + "title": "shutdown", + "description": "Attempt final delivery and permanently shut down the client. Call this for final process cleanup or when disposing of a client that will not be reused. It closes capture lanes, stops background workers and the feature flag poller, and tears down integrations. Do not call it at the end of each request or serverless invocation when the client is shared; call ``flush()`` before returning instead. A shut-down client cannot be reused; create a new client if needed. Normally this method blocks until queued events have been attempted and cleanup finishes. Failed or undrainable events may be dropped and reported through logging or ``on_error``; returning does not guarantee server receipt. Event draining has no timeout. Queued spans get one final flush with up to 30 seconds waiting for an in-flight flush, then a fresh 30-second drain budget; the last HTTP request is not cut short. Any spans it cannot send are discarded with a warning, as are spans still open. Lifecycle cleanup is attempted once, and cleanup failures are logged without retry. When called directly from an SDK callback such as ``on_error``, shutdown is deferred to avoid blocking the worker that invoked the callback. If the callback must coordinate a blocking shutdown, have it signal an application-owned thread and return before that thread calls shutdown. Do not wait inside the callback for another thread or task that calls a lifecycle method.", + "details": "", + "category": null, + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.shutdown()" + } + ] + }, + { + "id": "start_span", + "title": "start_span", + "description": "Start a span for distributed tracing. Alpha. Returns a span handle. Use it as a context manager to make it the active span for the block and end it on exit (recording a raised exception on the way out); or call ``end()`` yourself for a span that cannot wrap a block. Spans started inside the block nest under it automatically. Always returns a usable handle, even when tracing is off, so calling code never branches.", + "details": "", + "category": "Tracing", + "params": [ + { + "name": "name", + "description": "A low-cardinality operation name, e.g. ``GET /users/:id``. Variable values belong in attributes, not the name.", + "isOptional": true, + "type": "str" + }, + { + "name": "kind", + "description": "``internal`` (default), ``server``, ``client``, ``producer`` or ``consumer``.", + "isOptional": true, + "type": "str" + }, + { + "name": "attributes", + "description": "Initial attributes.", + "isOptional": true, + "type": "Mapping[str, Any]" + }, + { + "name": "parent", + "description": "A span handle, or an inbound W3C ``traceparent`` header value to continue a remote trace. Defaults to the active span. A forked child starts with no active span; pass the parent span to continue a trace across a fork.", + "isOptional": false, + "type": "Span" + }, + { + "name": "tracestate", + "description": "The inbound ``tracestate`` header accompanying a ``traceparent`` string ``parent``; preserved and propagated.", + "isOptional": true, + "type": "str" + }, + { + "name": "start_time", + "description": "A ``datetime`` or epoch seconds, to backdate the span.", + "isOptional": false, + "type": "datetime" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Span" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog = Posthog(\"\", traces={\"service_name\": \"checkout-api\"})\n\nwith posthog.start_span(\"POST /checkout\", parent=request.headers.get(\"traceparent\")) as span:\n span.set_attribute(\"plan\", user.plan)\n with posthog.start_span(\"db.query\", kind=\"client\"):\n ...\n outgoing_headers = {\"traceparent\": span.traceparent()}" + } + ] + }, + { + "id": "tag", + "title": "tag", + "description": "Add a tag to the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "name", + "description": "The tag key.", + "isOptional": true, + "type": "str" + }, + { + "name": "value", + "description": "The tag value.", + "isOptional": true, + "type": "Any" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + } + } + ] + }, + { + "id": "PostHogModule", + "title": "PostHog Module Functions", + "description": "Global functions available in the PostHog module", + "functions": [ + { + "id": "alias", + "title": "alias", + "description": "Associate user behaviour before and after they e.g. register, login, or perform some other identifying action.", + "details": "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?\". Particularly useful for associating user behaviour before and after they e.g. register, login, or perform some other identifying action.", + "category": "Identification", + "params": [ + { + "name": "previous_id", + "description": "The unique ID of the user before", + "isOptional": true, + "type": "Number" + }, + { + "name": "distinct_id", + "description": "The current unique id", + "isOptional": true, + "type": "str" + }, + { + "name": "timestamp", + "description": "Optional timestamp for the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC.", + "isOptional": false, + "type": "datetime" + }, + { + "name": "uuid", + "description": "Optional UUID for the event", + "isOptional": true, + "type": "str" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup", + "isOptional": true, + "type": "bool" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Alias user", + "code": "# Alias user\nfrom posthog import alias\nalias(previous_id='distinct_id', distinct_id='alias_id')" + } + ] + }, + { + "id": "capture", + "title": "capture", + "description": "Capture anything a user does within your system.", + "details": "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. A capture call requires an event name to specify the event. We recommend using [verb] [noun], like `movie played` or `movie updated` to easily identify what your events mean later on. Capture takes a number of optional arguments, which are defined by the `OptionalCaptureArgs` type.", + "category": "Events", + "params": [ + { + "name": "event", + "description": "The event name to specify the event **kwargs: Optional arguments including:", + "isOptional": true, + "type": "str" + }, + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalCaptureArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Context and capture usage", + "code": "# Context and capture usage\nfrom posthog import new_context, identify_context, tag_context, capture\n# Enter a new context (e.g. a request/response cycle, an instance of a background job, etc)\nwith new_context():\n # Associate this context with some user, by distinct_id\n identify_context('some user')\n\n # Capture an event, associated with the context-level distinct ID ('some user')\n capture('movie started')\n\n # Capture an event associated with some other user (overriding the context-level distinct ID)\n capture('movie joined', distinct_id='some-other-user')\n\n # Capture an event with some properties\n capture('movie played', properties={'movie_id': '123', 'category': 'romcom'})\n\n # Capture an event with some properties\n capture('purchase', properties={'product_id': '123', 'category': 'romcom'})\n # Capture an event with some associated group\n capture('purchase', groups={'company': 'id:5'})\n\n # Adding a tag to the current context will cause it to appear on all subsequent events\n tag_context('some-tag', 'some-value')\n\n capture('another-event') # Will be captured with `'some-tag': 'some-value'` in the properties dict" + }, + { + "id": "example_2", + "name": "Set event properties", + "code": "# Set event properties\nfrom posthog import capture\ncapture(\n \"user_signed_up\",\n distinct_id=\"distinct_id_of_the_user\",\n properties={\n \"login_type\": \"email\",\n \"is_free_trial\": \"true\"\n }\n)" + } + ] + }, + { + "id": "capture_ai", + "title": "capture_ai", + "description": "Capture an AI event on the dedicated AI capture endpoint. Beta: the signature is stable; operational limits (per-event size cap, batching, endpoint) may change without notice. Takes the same arguments and returns the same value as `capture()`: the event UUID, or None when the event was not admitted (disabled client, or dropped by `before_send`). The event is delivered on an isolated queue with its own consumer pool and a higher per-event size cap, posting to the dedicated AI ingestion endpoint. The payload is sent as given \u2014 no redaction or truncation is applied here.", + "details": "", + "category": "Events", + "params": [ + { + "name": "event", + "description": "The event name, normally one of the `$ai_*` event names. **kwargs: Same optional arguments as `capture()`.", + "isOptional": true, + "type": "str" + }, + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalCaptureArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import capture_ai\n\nuuid = capture_ai(\n \"$ai_generation\",\n distinct_id=\"user_123\",\n properties={\"$ai_model\": \"gpt-5\"},\n)" + } + ] + }, + { + "id": "capture_exception", + "title": "capture_exception", + "description": "Capture exceptions that happen in your code.", + "details": "Capture exception is idempotent - if it is called twice with the same exception instance, only a occurrence will be tracked in posthog. This is because, generally, contexts will cause exceptions to be captured automatically. However, to ensure you track an exception, if you catch and do not re-raise it, capturing it manually is recommended, unless you are certain it will have crossed a context boundary (e.g. by existing a `with posthog.new_context():` block already). If the passed exception was raised and caught, the captured stack trace will consist of every frame between where the exception was raised and the point at which it is captured (the \"traceback\"). If the passed exception was never raised, e.g. if you call `posthog.capture_exception(ValueError(\"Some Error\"))`, the stack trace captured will be the full stack trace at the moment the exception was captured. Note that heavy use of contexts will lead to truncated stack traces, as the exception will be captured by the context entered most recently, 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. `capture_exception` takes the same set of optional arguments as `capture`.", + "category": "Events", + "params": [ + { + "name": "exception", + "description": "The exception to capture. If not provided, the current exception is captured via `sys.exc_info()` **kwargs: Optional capture arguments including distinct_id, properties, timestamp, uuid, groups, flags, send_feature_flags, and disable_geoip. Overriding reserved exception properties through ``properties`` is deprecated and will stop working in the next major version.", + "isOptional": false, + "type": "BaseException" + }, + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalCaptureArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Capture exception", + "code": "# Capture exception\nfrom posthog import capture_exception\ntry:\n risky_operation()\nexcept Exception as e:\n capture_exception(e)" + } + ] + }, + { + "id": "evaluate_flags", + "title": "evaluate_flags", + "description": "Evaluate all feature flags for a user in a single call and return a :class:`FeatureFlagEvaluations` snapshot. Branch on ``.is_enabled()`` / ``.get_flag()`` and pass the same snapshot to ``capture()`` via the ``flags`` option so events carry the exact flag values the code branched on. Prefer this over repeated ``get_feature_flag()`` calls and over ``capture(send_feature_flags=True)`` \u2014 it consolidates flag evaluation into a single ``/flags`` request per incoming request.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The user's distinct ID. If ``None``, falls back to the context distinct_id. If still unresolvable, returns an empty snapshot.", + "isOptional": false, + "type": "Number" + }, + { + "name": "groups", + "description": "Mapping of group type to group key.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "Person properties to use for evaluation.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "Group properties keyed by group type.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "If ``True``, never fall back to remote evaluation and omit flags that cannot be evaluated locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup.", + "isOptional": true, + "type": "bool" + }, + { + "name": "flag_keys", + "description": "Optional list that scopes local evaluation, the underlying ``/flags`` request, and the returned snapshot. When omitted or ``None``, all flags are evaluated. An empty list returns an empty snapshot without evaluating flags. A requested key absent from loaded local definitions is included in one remote fallback per ``evaluate_flags`` call unless ``only_evaluate_locally`` is ``True``. If the server also does not know the key, it is omitted from the snapshot.", + "isOptional": true, + "type": "list[str]" + }, + { + "name": "device_id", + "description": "Optional device ID override. If not provided, falls back to the context device_id (which may be set via tracing headers). Used by experience-continuity flags to match users across distinct_id changes.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "FeatureFlagEvaluations" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import evaluate_flags, capture\nflags = evaluate_flags(\"user_123\", person_properties={\"plan\": \"enterprise\"})\nif flags.is_enabled(\"new-dashboard\"):\n render_new_dashboard()\ncapture(\"page_viewed\", distinct_id=\"user_123\", flags=flags)" + } + ] + }, + { + "id": "feature_enabled", + "title": "feature_enabled", + "description": "Use feature flags to enable or disable features for users.", + "details": "You can call `posthog.load_feature_flags()` before to make sure you're not doing unexpected requests.", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "The feature flag key", + "isOptional": true, + "type": "str" + }, + { + "name": "distinct_id", + "description": "The user's distinct ID", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "Groups mapping", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "Person properties", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "Group properties", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to evaluate only locally", + "isOptional": false, + "type": "bool" + }, + { + "name": "send_feature_flag_events", + "description": "Whether to send feature flag events", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "Optional device ID override for experience-continuity flags", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[bool]" + }, + "examples": [ + { + "id": "example_1", + "name": "Boolean feature flag", + "code": "# Boolean feature flag\nfrom posthog import feature_enabled, get_feature_flag_payload\nis_my_flag_enabled = feature_enabled('flag-key', 'distinct_id_of_your_user')\nif is_my_flag_enabled:\n matched_flag_payload = get_feature_flag_payload('flag-key', 'distinct_id_of_your_user')" + } + ] + }, + { + "id": "feature_flag_definitions", + "title": "feature_flag_definitions", + "description": "Returns loaded feature flags.", + "details": "Returns loaded feature flags, if any. Helpful for debugging what flag information you have loaded.", + "category": "Feature flags", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import feature_flag_definitions\ndefinitions = feature_flag_definitions()" + } + ] + }, + { + "id": "flush", + "title": "flush", + "description": "Attempt immediate delivery of queued events and ended spans, keeping the global client reusable. Call ``flush()`` before a short-lived request or serverless invocation returns when using the shared module-level client. Use ``shutdown()`` only for final process cleanup or a client that will not be reused. Flush does not stop the background workers or feature flag poller, and does not end open spans. Returning does not guarantee server receipt: pending work can remain after a timeout or span export failure, and failed events may be dropped by consumers. Calls made directly from SDK callbacks such as ``on_error`` are deferred rather than blocking the worker that invoked the callback.", + "details": "", + "category": "Client management", + "params": [ + { + "name": "timeout_seconds", + "description": "Seconds to wait for event queues to drain, shared across event lanes. Defaults to 10 seconds. Pass ``None`` to wait without a time limit; this does not guarantee successful delivery. Queued spans flush concurrently, waiting up to this many seconds for an in-flight flush before starting a fresh drain budget. A lock wait timeout leaves the in-flight flush to finish. At least one span request is attempted after acquiring the lock, even with no budget left; retriable failures are retried after backoff while budget remains, with one last attempt at the deadline. The last span request is not cut short and is bounded by the client's ``timeout`` in seconds, so this is not a strict wall-clock limit for the whole call. With ``None``, a retriable span failure is left for a later flush rather than retried within this call.", + "isOptional": true, + "type": "float" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "import posthog\nposthog.capture('event_name', distinct_id='user_id')\n# Before a serverless invocation returns; keep the global client open.\nposthog.flush(timeout_seconds=10)" + } + ] + }, + { + "id": "get_active_span", + "title": "get_active_span", + "description": "The span that is active in the current context, or ``None``. Alpha. Only entering a span (``with posthog.start_span(...) as span:``) makes it active; a span started manually is not. Use it to propagate the trace to the next service: ``span.traceparent()`` is the header value.", + "details": "", + "category": "Tracing", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[Span]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "span = posthog.get_active_span()\nif span is not None:\n headers[\"traceparent\"] = span.traceparent()" + } + ] + }, + { + "id": "get_all_flags", + "title": "get_all_flags", + "description": "Get all flags for a given user.", + "details": "Flags are key-value pairs where the key is the flag key and the value is the flag variant, or True, or False.", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The user's distinct ID", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "Groups mapping", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "Person properties", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "Group properties", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to evaluate only locally", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "Optional device ID override for experience-continuity flags", + "isOptional": true, + "type": "str" + }, + { + "name": "flag_keys_to_evaluate", + "description": "Optional list of flag keys to evaluate (evaluates all if None)", + "isOptional": true, + "type": "list[str]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[dict[str, Union[bool, str]]]" + }, + "examples": [ + { + "id": "example_1", + "name": "All flags for user", + "code": "# All flags for user\nfrom posthog import get_all_flags\nget_all_flags('distinct_id_of_your_user')" + } + ] + }, + { + "id": "get_all_flags_and_payloads", + "title": "get_all_flags_and_payloads", + "description": "Get all feature flag values and payloads for a user.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The user's distinct ID.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "Mapping of group type to group key.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "Person properties to use for evaluation.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "Group properties keyed by group type.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to evaluate only locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup.", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "Optional device ID override for experience-continuity flags.", + "isOptional": true, + "type": "str" + }, + { + "name": "flag_keys_to_evaluate", + "description": "Optional list of flag keys to evaluate. Evaluates all flags when omitted.", + "isOptional": true, + "type": "list[str]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "FlagsAndPayloads" + } + }, + { + "id": "get_feature_flag", + "title": "get_feature_flag", + "description": "Get feature flag variant for users. Used with experiments.", + "details": "`groups` are a mapping from group type to group key. So, if you have a group type of \"organization\" and a group key of \"5\", you would pass groups={\"organization\": \"5\"}. `group_properties` take the format: { group_type_name: { group_properties } }. So, for example, if you have the group type \"organization\" and the group key \"5\", with the properties name, and employee count, you'll send these as: group_properties={\"organization\": {\"name\": \"PostHog\", \"employees\": 11}}.", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "The feature flag key", + "isOptional": true, + "type": "str" + }, + { + "name": "distinct_id", + "description": "The user's distinct ID", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "Groups mapping from group type to group key", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "Person properties", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "Group properties in format { group_type_name: { group_properties } }", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to evaluate only locally", + "isOptional": false, + "type": "bool" + }, + { + "name": "send_feature_flag_events", + "description": "Whether to send feature flag events", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "Optional device ID override for experience-continuity flags", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Union[bool, str, any]" + }, + "examples": [ + { + "id": "example_1", + "name": "Multivariate feature flag", + "code": "# Multivariate feature flag\nfrom posthog import get_feature_flag, get_feature_flag_payload\nenabled_variant = get_feature_flag('flag-key', 'distinct_id_of_your_user')\nif enabled_variant == 'variant-key':\n matched_flag_payload = get_feature_flag_payload('flag-key', 'distinct_id_of_your_user')" + } + ] + }, + { + "id": "get_feature_flag_evaluation_runtime", + "title": "get_feature_flag_evaluation_runtime", + "description": "Return where a locally loaded feature flag is meant to be evaluated.", + "details": "Reads the `evaluation_runtime` each flag definition carries, so no extra request is made. Returns `None` when local evaluation has not loaded a definition for this key. A definition that carries no runtime reports `FeatureFlagEvaluationRuntime.ALL`, the default PostHog applies.", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[FeatureFlagEvaluationRuntime]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import FeatureFlagEvaluationRuntime, get_feature_flag_evaluation_runtime\nruntime = get_feature_flag_evaluation_runtime(\"my-flag\")" + } + ] + }, + { + "id": "get_feature_flag_keys_by_evaluation_runtime", + "title": "get_feature_flag_keys_by_evaluation_runtime", + "description": "Return the keys of locally loaded flags that a runtime can evaluate.", + "details": "A flag set to `FeatureFlagEvaluationRuntime.ALL` suits either runtime, so it is returned for `CLIENT` and for `SERVER`. Use this to decide which flags to hand to a browser when a backend serves flags to its own frontend.", + "category": "Feature flags", + "params": [ + { + "name": "evaluation_runtime", + "description": "", + "isOptional": true, + "type": "FeatureFlagEvaluationRuntime" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "list[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import FeatureFlagEvaluationRuntime, get_feature_flag_keys_by_evaluation_runtime\nclient_keys = get_feature_flag_keys_by_evaluation_runtime(FeatureFlagEvaluationRuntime.CLIENT)" + } + ] + }, + { + "id": "get_feature_flag_payload", + "title": "get_feature_flag_payload", + "description": "Get the payload associated with a feature flag value. Deprecated for new code. Prefer ``evaluate_flags()`` and ``flags.get_flag_payload(key)`` so flag evaluation happens once per request.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "The feature flag key.", + "isOptional": true, + "type": "str" + }, + { + "name": "distinct_id", + "description": "The user's distinct ID.", + "isOptional": true, + "type": "Number" + }, + { + "name": "match_value", + "description": "Optional flag value to use when selecting a payload.", + "isOptional": false, + "type": "bool" + }, + { + "name": "groups", + "description": "Mapping of group type to group key.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "Person properties to use for evaluation.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "Group properties keyed by group type.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to evaluate only locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "send_feature_flag_events", + "description": "Whether to send a $feature_flag_called event.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup.", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "Optional device ID override for experience-continuity flags.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[object]" + } + }, + { + "id": "get_feature_flag_result", + "title": "get_feature_flag_result", + "description": "Get a FeatureFlagResult object which contains the flag result and payload. This method evaluates a feature flag and returns a FeatureFlagResult object containing: - enabled: Whether the flag is enabled - variant: The variant value if the flag has variants - payload: The payload associated with the flag (automatically deserialized from JSON) - key: The flag key - reason: Why the flag was enabled/disabled", + "details": "", + "category": null, + "params": [ + { + "name": "key", + "description": "The feature flag key.", + "isOptional": true, + "type": "str" + }, + { + "name": "distinct_id", + "description": "The user's distinct ID.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "Mapping of group type to group key.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "Person properties to use for evaluation.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "Group properties keyed by group type.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to evaluate only locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "send_feature_flag_events", + "description": "Whether to send a $feature_flag_called event.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup.", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "Optional device ID override for experience-continuity flags.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[FeatureFlagResult]" + } + }, + { + "id": "get_remote_config_payload", + "title": "get_remote_config_payload", + "description": "Get the payload for a remote config feature flag.", + "details": "", + "category": null, + "params": [ + { + "name": "key", + "description": "The key of the feature flag", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "get_tags", + "title": "get_tags", + "description": "Get all tags from the current context. Returns: Dict of all tags in the current context", + "details": "", + "category": "Contexts", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "dict[str, Any]" + } + }, + { + "id": "group_identify", + "title": "group_identify", + "description": "Set properties on a group.", + "details": "", + "category": "Identification", + "params": [ + { + "name": "group_type", + "description": "Type of your group. Required - the call is dropped with a warning if it is missing or empty.", + "isOptional": true, + "type": "str" + }, + { + "name": "group_key", + "description": "Unique identifier of the group. Required - the call is dropped with a warning if it is missing or empty.", + "isOptional": true, + "type": "str" + }, + { + "name": "properties", + "description": "Properties to set on the group", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "timestamp", + "description": "Optional timestamp for the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC.", + "isOptional": false, + "type": "datetime" + }, + { + "name": "uuid", + "description": "Optional UUID for the event", + "isOptional": true, + "type": "str" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup", + "isOptional": true, + "type": "bool" + }, + { + "name": "distinct_id", + "description": "Optional distinct ID of the user performing the action", + "isOptional": false, + "type": "Number" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Group identify", + "code": "# Group identify\nfrom posthog import group_identify\ngroup_identify('company', 'company_id_in_your_db', {\n 'name': 'Awesome Inc.',\n 'employees': 11\n})" + } + ] + }, + { + "id": "identify_context", + "title": "identify_context", + "description": "Identify the current context with a distinct ID.", + "details": "", + "category": "Identification", + "params": [ + { + "name": "distinct_id", + "description": "The distinct ID to associate with the current context and its children", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import identify_context\nidentify_context(\"user_123\")" + } + ] + }, + { + "id": "join", + "title": "join", + "description": "Attempt to process queued events and stop the client's background workers. Use `shutdown()` directly in most cases. Failed or undrainable events may be dropped and reported through logging or ``on_error``; returning does not guarantee server receipt. Lifecycle cleanup is attempted once, and cleanup failures are logged without retry.", + "details": "", + "category": "Client management", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import join\njoin()" + } + ] + }, + { + "id": "load_feature_flags", + "title": "load_feature_flags", + "description": "Load feature flag definitions from PostHog.", + "details": "", + "category": "Feature flags", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import load_feature_flags\nload_feature_flags()" + } + ] + }, + { + "id": "new_context", + "title": "new_context", + "description": "Create a new context scope that will be active for the duration of the with block.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "fresh", + "description": "Whether to start with a fresh context (default: False)", + "isOptional": false, + "type": "bool" + }, + { + "name": "capture_exceptions", + "description": "Whether to capture exceptions raised within the context. If omitted, defaults to the relevant client's exception autocapture setting.", + "isOptional": true, + "type": "bool" + }, + { + "name": "client", + "description": "Optional Posthog client instance to use for this context (default: None)", + "isOptional": true, + "type": "Client" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import new_context, tag, capture\nwith new_context():\n tag(\"request_id\", \"123\")\n capture(\"event_name\", properties={\"property\": \"value\"})" + } + ] + }, + { + "id": "scoped", + "title": "scoped", + "description": "Decorator that creates a new context for the function.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "fresh", + "description": "Whether to start with a fresh context (default: False)", + "isOptional": false, + "type": "bool" + }, + { + "name": "capture_exceptions", + "description": "Whether to capture and track exceptions with posthog error tracking. If omitted, defaults to the global exception autocapture setting.", + "isOptional": true, + "type": "bool" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import scoped, tag, capture\n@scoped()\ndef process_payment(payment_id):\n tag(\"payment_id\", payment_id)\n capture(\"payment_started\")" + } + ] + }, + { + "id": "set", + "title": "set", + "description": "Set properties on a user record.", + "details": "This will overwrite previous people property values. Generally operates similar to `capture`, with distinct_id being an optional argument, defaulting to the current context's distinct ID. If there is no context-level distinct ID, and no override distinct_id is passed, this function will do nothing. Context tags are folded into $set properties, so tagging the current context and then calling `set` will cause those tags to be set on the user (unlike capture, which causes them to just be set on the event).", + "category": "Identification", + "params": [ + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalSetArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Set person properties", + "code": "# Set person properties\nfrom posthog import set\nset(distinct_id='distinct_id', properties={'name': 'Max Hedgehog'})" + } + ] + }, + { + "id": "set_capture_exception_code_variables_context", + "title": "set_capture_exception_code_variables_context", + "description": "Override code-variable capture for exceptions in the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "enabled", + "description": "Whether exceptions captured in this context should include local variable values from stack frames.", + "isOptional": true, + "type": "bool" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "set_code_variables_detect_secrets_context", + "title": "set_code_variables_detect_secrets_context", + "description": "Whether to apply entropy-based secret detection as a last-resort redaction of high-entropy values (API keys, tokens, strong passwords) in captured code variables for the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "enabled", + "description": "", + "isOptional": true, + "type": "bool" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "set_code_variables_ignore_patterns_context", + "title": "set_code_variables_ignore_patterns_context", + "description": "Override code-variable ignore patterns for exceptions in the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "ignore_patterns", + "description": "Variable-name patterns that should be omitted entirely when code variables are captured.", + "isOptional": true, + "type": "list[str]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "set_code_variables_mask_patterns_context", + "title": "set_code_variables_mask_patterns_context", + "description": "Override code-variable mask patterns for exceptions in the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "mask_patterns", + "description": "Variable-name patterns whose values should be replaced with ``***`` when code variables are captured.", + "isOptional": true, + "type": "list[str]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "set_code_variables_mask_url_credentials_context", + "title": "set_code_variables_mask_url_credentials_context", + "description": "Whether to scrub credentials embedded in URLs/DSNs (e.g. user:pass@host) from captured code variables for the current context.", + "details": "", + "category": null, + "params": [ + { + "name": "enabled", + "description": "", + "isOptional": true, + "type": "bool" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "set_context_device_id", + "title": "set_context_device_id", + "description": "Set the device ID for the current context, associating all feature flag requests in this or child contexts with the given device ID.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "device_id", + "description": "The device ID to associate with the current context and its children", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import set_context_device_id\nset_context_device_id(\"device_123\")" + } + ] + }, + { + "id": "set_context_session", + "title": "set_context_session", + "description": "Set the session ID for the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "session_id", + "description": "The session ID to associate with the current context and its children", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import set_context_session\nset_context_session(\"session_123\")" + } + ] + }, + { + "id": "set_once", + "title": "set_once", + "description": "Set properties on a user record, only if they do not yet exist.", + "details": "This will not overwrite previous people property values, unlike `set`. Otherwise, operates in an identical manner to `set`.", + "category": "Identification", + "params": [ + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalSetArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Set property once", + "code": "# Set property once\nfrom posthog import set_once\nset_once(distinct_id='distinct_id', properties={'initial_url': '/blog'})" + } + ] + }, + { + "id": "setup", + "title": "setup", + "description": "Create or return the global PostHog client configured by module settings. Most applications should either instantiate ``Posthog`` directly or set ``posthog.api_key``/other module settings before calling top-level helpers. ``setup()`` is called automatically by global APIs such as ``capture()``. Returns: The global ``Client`` instance. If both ``api_key`` and ``project_api_key`` are missing or blank, the client is disabled and module-level calls become no-ops.", + "details": "", + "category": "Initialization", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Client" + } + }, + { + "id": "shutdown", + "title": "shutdown", + "description": "Attempt final delivery and permanently shut down the global client. Call this for final process cleanup, not at the end of each request or serverless invocation that shares the module-level client. Use ``flush()`` before returning from those invocations instead. Shutdown closes capture lanes, stops background workers and the feature flag poller, and tears down integrations. The global client is not recreated automatically after shutdown. This normally blocks until queued events have been attempted and cleanup finishes. Failed or undrainable events may be dropped and reported through logging or ``on_error``; returning does not guarantee server receipt. Event draining has no timeout. Queued spans get a final flush with up to 30 seconds waiting for an in-flight flush, then a fresh 30-second drain budget; the last HTTP request is not cut short. Unsent queued spans and still-open spans are discarded with a warning. Lifecycle cleanup is attempted once, and cleanup failures are logged without retry. Calls made directly from SDK callbacks such as ``on_error`` are deferred to avoid deadlocking the worker. If blocking completion is required, signal an application-owned thread, return from the callback, and call ``shutdown()`` from that thread. Do not wait inside a callback for another thread or task calling a lifecycle method.", + "details": "", + "category": "Client management", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import shutdown\nshutdown()" + } + ] + }, + { + "id": "start_span", + "title": "start_span", + "description": "Start a span for distributed tracing. Alpha. Returns a span handle. Use it as a context manager to make it the active span for the block and end it on exit (recording a raised exception on the way out); or call ``end()`` yourself for a span that cannot wrap a block. Spans started inside the block nest under it automatically. Always returns a usable handle, even when tracing is off, so calling code never branches.", + "details": "", + "category": "Tracing", + "params": [ + { + "name": "name", + "description": "A low-cardinality operation name, e.g. ``GET /users/:id``. Variable values belong in attributes, not the name.", + "isOptional": true, + "type": "str" + }, + { + "name": "kind", + "description": "``internal`` (default), ``server``, ``client``, ``producer`` or ``consumer``.", + "isOptional": true, + "type": "str" + }, + { + "name": "attributes", + "description": "Initial attributes.", + "isOptional": true, + "type": "Mapping[str, Any]" + }, + { + "name": "parent", + "description": "A span handle, or an inbound W3C ``traceparent`` header value to continue a remote trace. Defaults to the active span.", + "isOptional": false, + "type": "Span" + }, + { + "name": "tracestate", + "description": "The inbound ``tracestate`` header accompanying a ``traceparent`` string ``parent``; preserved and propagated.", + "isOptional": true, + "type": "str" + }, + { + "name": "start_time", + "description": "A ``datetime`` or epoch seconds, to backdate the span.", + "isOptional": false, + "type": "datetime" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Span" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "import posthog\nposthog.traces = {\"service_name\": \"checkout-api\"}\n\nwith posthog.start_span(\"POST /checkout\", parent=request.headers.get(\"traceparent\")) as span:\n span.set_attribute(\"plan\", user.plan)\n with posthog.start_span(\"db.query\", kind=\"client\"):\n ...\n outgoing_headers = {\"traceparent\": span.traceparent()}" + } + ] + }, + { + "id": "tag", + "title": "tag", + "description": "Add a tag to the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "name", + "description": "The tag key", + "isOptional": true, + "type": "str" + }, + { + "name": "value", + "description": "The tag value", + "isOptional": true, + "type": "Any" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import tag\ntag(\"user_id\", \"123\")" + } + ] + } + ] + } + ], + "categories": [ + "Initialization", + "Identification", + "Capture", + "Error Tracking", + "Feature flags", + "Tracing", + "Contexts", + "Events", + "Client management" + ] +} \ No newline at end of file diff --git a/references/posthog-python-references-latest.json b/references/posthog-python-references-latest.json index 966c6f29..70871253 100644 --- a/references/posthog-python-references-latest.json +++ b/references/posthog-python-references-latest.json @@ -2,7 +2,7 @@ "id": "posthog-python", "hogRef": "0.3", "info": { - "version": "7.76.1", + "version": "7.77.0", "id": "posthog-python", "title": "PostHog Python SDK", "description": "Integrate PostHog into any python application.", From fd18bcc3904f799c11f66e899737d6a375ba6e8e Mon Sep 17 00:00:00 2001 From: Georgis Andonis <6430745+gesh@users.noreply.github.com> Date: Wed, 28 Oct 2026 15:54:22 +0200 Subject: [PATCH 31/60] feat(mcp): add a send_tools_list_event option (#1116) MCPAnalyticsOptions(send_tools_list_event=False) stops $mcp_tools_list. The option is on by default. Clients list tools on every reconnect, so the listing event is about as common as $mcp_tool_call. With the option off, record_tools_list returns early, which also drops a failed listing's $exception. Nothing else changes: tools keep their injected arguments, and the virtual tools stay listed. The name says "event" to match send_initialize_event. PostHogMCP.capture_tools_list() already captures the event by hand. Co-authored-by: Claude Opus 5.5 (cherry picked from commit 351da59c252afb7bffed79d21c23aa7e2539a38e) --- .../mcp-capture-tools-list-option.md | 5 ++ posthog/mcp/README.md | 3 + posthog/mcp/_instrumentation.py | 2 + posthog/mcp/types.py | 4 ++ .../test/mcp/test_send_tools_list_event.py | 68 +++++++++++++++++++ references/public_api_snapshot.txt | 3 +- 6 files changed, 84 insertions(+), 1 deletion(-) create mode 100644 .sampo/changesets/mcp-capture-tools-list-option.md create mode 100644 posthog/test/mcp/test_send_tools_list_event.py diff --git a/.sampo/changesets/mcp-capture-tools-list-option.md b/.sampo/changesets/mcp-capture-tools-list-option.md new file mode 100644 index 00000000..0208e762 --- /dev/null +++ b/.sampo/changesets/mcp-capture-tools-list-option.md @@ -0,0 +1,5 @@ +--- +pypi/posthog: minor +--- + +Add a `send_tools_list_event` option to MCP analytics. It is on by default. Set it to `False` to stop sending `$mcp_tools_list`. diff --git a/posthog/mcp/README.md b/posthog/mcp/README.md index fd5cc8bf..7dd4e70f 100644 --- a/posthog/mcp/README.md +++ b/posthog/mcp/README.md @@ -97,6 +97,9 @@ while the option is off. instrument(server, posthog, MCPAnalyticsOptions(send_initialize_event=False)) ``` +Set `send_tools_list_event=False` to stop sending `$mcp_tools_list`. Nothing else changes: tools +keep their injected arguments, and the virtual tools stay listed. + `identify` and `event_properties` run for the handshake too. On MCP SDK 1.x the SDK has no request context for it, so `extra["ctx"]` carries only `.request`. `get_request_headers(extra)` works the same as for any other request. diff --git a/posthog/mcp/_instrumentation.py b/posthog/mcp/_instrumentation.py index 37877380..cd87dd4a 100644 --- a/posthog/mcp/_instrumentation.py +++ b/posthog/mcp/_instrumentation.py @@ -1449,6 +1449,8 @@ async def record_tools_list( protocol_version: Optional[str] = None, extra: Optional[Dict[str, Any]] = None, ) -> None: + if not data.options.send_tools_list_event: + return try: event: Dict[str, Any] = { "event_type": MCPAnalyticsEventType.MCP_TOOLS_LIST, diff --git a/posthog/mcp/types.py b/posthog/mcp/types.py index 055939d4..17bb0b83 100644 --- a/posthog/mcp/types.py +++ b/posthog/mcp/types.py @@ -250,6 +250,10 @@ class MCPAnalyticsOptions: # Send `$mcp_initialize` for each `initialize` handshake. On by default. # False sends no `$mcp_initialize`. Nothing else changes. send_initialize_event: bool = True + # Send `$mcp_tools_list` for each `tools/list`. On by default. False sends + # no `$mcp_tools_list`. Nothing else changes: tools keep their injected + # arguments, and the virtual tools stay listed. + send_tools_list_event: bool = True @dataclass diff --git a/posthog/test/mcp/test_send_tools_list_event.py b/posthog/test/mcp/test_send_tools_list_event.py new file mode 100644 index 00000000..7f41b3f5 --- /dev/null +++ b/posthog/test/mcp/test_send_tools_list_event.py @@ -0,0 +1,68 @@ +"""``send_tools_list_event=False`` sends no ``$mcp_tools_list``, and nothing else changes. + +Reuses the real-HTTP fleet from ``test_initialize_handshake.py``, so it runs for +every server shape on both SDK majors. +""" + +from __future__ import annotations + +import pytest + +from posthog.mcp import MCPAnalyticsOptions, instrument +from posthog.test.mcp._helpers import ( + MCP_MAJOR, + FakeClient, + events_named as _events, + flush_background as _flush, +) +from posthog.test.mcp.test_initialize_handshake import ( + KINDS, + call_add, + fleet, + handshake, + list_tools, +) + + +@pytest.mark.parametrize("kind", KINDS) +@pytest.mark.parametrize("capture", [True, False]) +async def test_send_tools_list_event_controls_only_the_listing_event(kind, capture): + client = FakeClient() + options = MCPAnalyticsOptions( + enable_conversation_id=False, send_tools_list_event=capture + ) + async with fleet(client, kind=kind, options=options) as lb: + headers, _ = await handshake(lb) + listing = (await list_tools(lb, headers)).json()["result"] + await call_add(lb, headers) + await _flush() + + assert len(_events(client, "$mcp_tools_list")) == (1 if capture else 0) + # The listing itself is unchanged: the SDK still injects `context`. + (tool,) = [t for t in listing["tools"] if t["name"] == "add"] + assert "context" in tool["inputSchema"]["properties"] + (call,) = _events(client, "$mcp_tool_call") + assert call["properties"]["$mcp_intent"] == "adding" + assert len(_events(client, "$mcp_initialize")) == 1 + + +@pytest.mark.skipif(MCP_MAJOR >= 2, reason="the request_handlers registry is mcp 1.x") +async def test_send_tools_list_event_off_also_drops_a_failed_listing(): + import mcp.types as mcp_types + from mcp.server.lowlevel import Server + + server = Server("failing-listing") + + @server.list_tools() + async def list_tools_handler(): + raise RuntimeError("listing failed") + + client = FakeClient() + instrument(server, client, MCPAnalyticsOptions(send_tools_list_event=False)) + handler = server.request_handlers[mcp_types.ListToolsRequest] + with pytest.raises(RuntimeError): + await handler(mcp_types.ListToolsRequest(method="tools/list")) + await _flush() + + assert _events(client, "$mcp_tools_list") == [] + assert _events(client, "$exception") == [] diff --git a/references/public_api_snapshot.txt b/references/public_api_snapshot.txt index 9a6aa497..960d020d 100644 --- a/references/public_api_snapshot.txt +++ b/references/public_api_snapshot.txt @@ -1049,6 +1049,7 @@ attribute posthog.mcp.types.MCPAnalyticsOptions.report_missing: bool = False attribute posthog.mcp.types.MCPAnalyticsOptions.resolve_input_aliases: Optional[ResolveInputAliasesFn] = None attribute posthog.mcp.types.MCPAnalyticsOptions.resolve_original_tool: Optional[Callable[[str], Any]] = None attribute posthog.mcp.types.MCPAnalyticsOptions.send_initialize_event: bool = True +attribute posthog.mcp.types.MCPAnalyticsOptions.send_tools_list_event: bool = True attribute posthog.mcp.types.MCPAnalyticsOptions.server_build: Optional[str] = None attribute posthog.mcp.types.MCPAnalyticsOptions.should_record_input_key: Optional[ShouldRecordInputKeyFn] = None attribute posthog.mcp.types.PreparedToolCall.args: Optional[JsonRecord] = None @@ -1302,7 +1303,7 @@ class posthog.mcp.types.CollectFeedbackOptions(tool_name: Optional[str] = None, class posthog.mcp.types.FeedbackReport(feedback_type: str = 'other', summary: str = '', sentiment: Optional[str] = None, friction_points: Optional[str] = None, suggested_improvement: Optional[str] = None, details: Optional[str] = None, tool_name: Optional[str] = None, task_completed: Optional[bool] = None, extras: JsonRecord = dict(), raw: JsonRecord = dict()) class posthog.mcp.types.MCPAnalyticsContextOptions(description: Optional[str] = None) class posthog.mcp.types.MCPAnalyticsModelOptions(description: Optional[str] = None) -class posthog.mcp.types.MCPAnalyticsOptions(logger: Optional[LoggerFn] = None, report_missing: bool = False, missing_capability_tool_name: Optional[str] = None, enable_conversation_id: bool = True, enable_exception_autocapture: bool = True, context: Union[bool, MCPAnalyticsContextOptions] = True, identify: Optional[Union[IdentifyFn, UserIdentity]] = None, intent_fallback: Optional[IntentFallbackFn] = None, before_send: Optional[BeforeSendFn] = None, event_properties: Optional[EventPropertiesFn] = None, capture_model: Union[bool, MCPAnalyticsModelOptions] = True, collect_feedback: Union[bool, CollectFeedbackOptions] = False, server_build: Optional[str] = None, should_record_input_key: Optional[ShouldRecordInputKeyFn] = None, resolve_input_aliases: Optional[ResolveInputAliasesFn] = None, resolve_original_tool: Optional[Callable[[str], Any]] = None, send_initialize_event: bool = True) +class posthog.mcp.types.MCPAnalyticsOptions(logger: Optional[LoggerFn] = None, report_missing: bool = False, missing_capability_tool_name: Optional[str] = None, enable_conversation_id: bool = True, enable_exception_autocapture: bool = True, context: Union[bool, MCPAnalyticsContextOptions] = True, identify: Optional[Union[IdentifyFn, UserIdentity]] = None, intent_fallback: Optional[IntentFallbackFn] = None, before_send: Optional[BeforeSendFn] = None, event_properties: Optional[EventPropertiesFn] = None, capture_model: Union[bool, MCPAnalyticsModelOptions] = True, collect_feedback: Union[bool, CollectFeedbackOptions] = False, server_build: Optional[str] = None, should_record_input_key: Optional[ShouldRecordInputKeyFn] = None, resolve_input_aliases: Optional[ResolveInputAliasesFn] = None, resolve_original_tool: Optional[Callable[[str], Any]] = None, send_initialize_event: bool = True, send_tools_list_event: bool = True) class posthog.mcp.types.PreparedToolCall(args: Optional[JsonRecord] = None, intent: Optional[str] = None, intent_source: Optional[str] = None, is_missing_capability: bool = False, llm_model: Optional[str] = None, llm_model_source: Optional[MCPAnalyticsModelSource] = None, is_feedback: bool = False, feedback_report: Optional[FeedbackReport] = None, session_id: Optional[str] = None, conversation_id: Optional[str] = None, _conversation_state: Optional[PreparedConversationState] = None) class posthog.mcp.types.ToolInputOptions(should_record_input_key: Optional[ShouldRecordInputKeyFn] = None, input_aliases: Optional[InputAliasMap] = None) class posthog.mcp.types.UserIdentity(distinct_id: str, properties: Optional[JsonRecord] = None, groups: Optional[Dict[str, str]] = None) From 5bd634a88a4a4bbed6f244aedc89d56c2f61dc5d Mon Sep 17 00:00:00 2001 From: "releaser-posthog-python[bot]" <262414804+releaser-posthog-python[bot]@users.noreply.github.com> Date: Wed, 28 Oct 2026 14:01:03 +0000 Subject: [PATCH 32/60] chore: Release v7.78.0 [skip ci] (cherry picked from commit 39110204fdf2aceb1e00f66f18bd5656f0783aff) --- .sampo/changesets/mcp-capture-tools-list-option.md | 5 ----- openfeature-provider/CHANGELOG.md | 6 ++++++ openfeature-provider/pyproject.toml | 2 +- posthog/CHANGELOG.md | 6 ++++++ posthog/version.py | 2 +- pyproject.toml | 2 +- uv.lock | 4 ++-- 7 files changed, 17 insertions(+), 10 deletions(-) delete mode 100644 .sampo/changesets/mcp-capture-tools-list-option.md diff --git a/.sampo/changesets/mcp-capture-tools-list-option.md b/.sampo/changesets/mcp-capture-tools-list-option.md deleted file mode 100644 index 0208e762..00000000 --- a/.sampo/changesets/mcp-capture-tools-list-option.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -pypi/posthog: minor ---- - -Add a `send_tools_list_event` option to MCP analytics. It is on by default. Set it to `False` to stop sending `$mcp_tools_list`. diff --git a/openfeature-provider/CHANGELOG.md b/openfeature-provider/CHANGELOG.md index 9a8af747..8ab8f1da 100644 --- a/openfeature-provider/CHANGELOG.md +++ b/openfeature-provider/CHANGELOG.md @@ -4,6 +4,12 @@ All notable changes to `openfeature-provider-posthog` are documented here. This file is maintained by [Sampo](https://github.com/bruits/sampo) from changesets in `.sampo/changesets/` that target `pypi/openfeature-provider-posthog`. +## 0.1.129 — 2026-10-28 + +### Patch changes + +- Updated dependencies: posthog@7.78.0 + ## 0.1.128 — 2026-10-28 ### Patch changes diff --git a/openfeature-provider/pyproject.toml b/openfeature-provider/pyproject.toml index 35d83d81..be34d793 100644 --- a/openfeature-provider/pyproject.toml +++ b/openfeature-provider/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "openfeature-provider-posthog" -version = "0.1.128" +version = "0.1.129" description = "Official PostHog provider for the OpenFeature Python SDK." readme = "README.md" authors = [{ name = "PostHog", email = "engineering@posthog.com" }] diff --git a/posthog/CHANGELOG.md b/posthog/CHANGELOG.md index 5cf1cd2b..c8eb695a 100644 --- a/posthog/CHANGELOG.md +++ b/posthog/CHANGELOG.md @@ -1,5 +1,11 @@ # posthog +## 7.78.0 — 2026-10-28 + +### Minor changes + +- [351da59](https://github.com/posthog/posthog-python/commit/351da59c252afb7bffed79d21c23aa7e2539a38e) Add a `send_tools_list_event` option to MCP analytics. It is on by default. Set it to `False` to stop sending `$mcp_tools_list`. — Thanks @gesh! + ## 7.77.0 — 2026-10-28 ### Minor changes diff --git a/posthog/version.py b/posthog/version.py index 7b925de0..a1744501 100644 --- a/posthog/version.py +++ b/posthog/version.py @@ -1 +1 @@ -VERSION = "7.77.0" +VERSION = "7.78.0" diff --git a/pyproject.toml b/pyproject.toml index 92fc154c..d6533c0b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "posthog" -version = "7.77.0" +version = "7.78.0" description = "Integrate PostHog into any python application." authors = [{ name = "PostHog", email = "engineering@posthog.com" }] maintainers = [{ name = "PostHog", email = "engineering@posthog.com" }] diff --git a/uv.lock b/uv.lock index 9329c839..e192a870 100644 --- a/uv.lock +++ b/uv.lock @@ -2656,7 +2656,7 @@ wheels = [ [[package]] name = "openfeature-provider-posthog" -version = "0.1.128" +version = "0.1.129" source = { editable = "openfeature-provider" } dependencies = [ { name = "openfeature-sdk" }, @@ -3045,7 +3045,7 @@ wheels = [ [[package]] name = "posthog" -version = "7.77.0" +version = "7.78.0" source = { editable = "." } dependencies = [ { name = "distro" }, From 178927e6668b78fcdb0a14d21cb0cee5ad4801be Mon Sep 17 00:00:00 2001 From: "releaser-posthog-python[bot]" <262414804+releaser-posthog-python[bot]@users.noreply.github.com> Date: Wed, 28 Oct 2026 14:01:09 +0000 Subject: [PATCH 33/60] Update generated references (cherry picked from commit 75a17943805604a040ac2f204fd74e3fbeb501a3) --- .../posthog-python-references-7.78.0.json | 3548 +++++++++++++++++ .../posthog-python-references-latest.json | 2 +- 2 files changed, 3549 insertions(+), 1 deletion(-) create mode 100644 references/posthog-python-references-7.78.0.json diff --git a/references/posthog-python-references-7.78.0.json b/references/posthog-python-references-7.78.0.json new file mode 100644 index 00000000..66be86ee --- /dev/null +++ b/references/posthog-python-references-7.78.0.json @@ -0,0 +1,3548 @@ +{ + "id": "posthog-python", + "hogRef": "0.3", + "info": { + "version": "7.78.0", + "id": "posthog-python", + "title": "PostHog Python SDK", + "description": "Integrate PostHog into any python application.", + "slugPrefix": "posthog-python", + "specUrl": "https://github.com/PostHog/posthog-python" + }, + "types": [ + { + "id": "FeatureFlag", + "name": "FeatureFlag", + "path": "posthog.types.FeatureFlag", + "properties": [ + { + "name": "key", + "type": "str", + "description": "Field: key" + }, + { + "name": "enabled", + "type": "bool", + "description": "Field: enabled" + }, + { + "name": "variant", + "type": "Optional[str]", + "description": "Field: variant" + }, + { + "name": "reason", + "type": "Optional[FlagReason]", + "description": "Field: reason" + }, + { + "name": "metadata", + "type": "Union[FlagMetadata, LegacyFlagMetadata]", + "description": "Field: metadata" + } + ], + "example": "" + }, + { + "id": "FeatureFlagResult", + "name": "FeatureFlagResult", + "path": "posthog.types.FeatureFlagResult", + "properties": [ + { + "name": "key", + "type": "str", + "description": "Field: key" + }, + { + "name": "enabled", + "type": "bool", + "description": "Field: enabled" + }, + { + "name": "variant", + "type": "Optional[str]", + "description": "Field: variant" + }, + { + "name": "payload", + "type": "Optional[Any]", + "description": "Field: payload" + }, + { + "name": "reason", + "type": "Optional[str]", + "description": "Field: reason" + } + ], + "example": "" + }, + { + "id": "FlagMetadata", + "name": "FlagMetadata", + "path": "posthog.types.FlagMetadata", + "properties": [ + { + "name": "id", + "type": "int", + "description": "Field: id" + }, + { + "name": "payload", + "type": "Optional[str]", + "description": "Field: payload" + }, + { + "name": "version", + "type": "int", + "description": "Field: version" + }, + { + "name": "description", + "type": "str", + "description": "Field: description" + }, + { + "name": "has_experiment", + "type": "Optional[bool]", + "description": "Field: has_experiment" + } + ], + "example": "" + }, + { + "id": "FlagReason", + "name": "FlagReason", + "path": "posthog.types.FlagReason", + "properties": [ + { + "name": "code", + "type": "str", + "description": "Field: code" + }, + { + "name": "condition_index", + "type": "Optional[int]", + "description": "Field: condition_index" + }, + { + "name": "description", + "type": "str", + "description": "Field: description" + } + ], + "example": "" + }, + { + "id": "FlagsAndPayloads", + "name": "FlagsAndPayloads", + "path": "posthog.types.FlagsAndPayloads", + "properties": [ + { + "name": "featureFlags", + "type": "Optional[dict[str, Union[bool, str]]]", + "description": "Field: featureFlags" + }, + { + "name": "featureFlagPayloads", + "type": "Optional[dict[str, Any]]", + "description": "Field: featureFlagPayloads" + } + ], + "example": "" + }, + { + "id": "FlagsResponse", + "name": "FlagsResponse", + "path": "posthog.types.FlagsResponse", + "properties": [ + { + "name": "flags", + "type": "dict[str, FeatureFlag]", + "description": "Field: flags" + }, + { + "name": "errorsWhileComputingFlags", + "type": "bool", + "description": "Field: errorsWhileComputingFlags" + }, + { + "name": "requestId", + "type": "str", + "description": "Field: requestId" + }, + { + "name": "quotaLimit", + "type": "Optional[list[str]]", + "description": "Field: quotaLimit" + }, + { + "name": "evaluatedAt", + "type": "Optional[int]", + "description": "Field: evaluatedAt" + }, + { + "name": "minimalFlagCalledEvents", + "type": "bool", + "description": "Field: minimalFlagCalledEvents" + } + ], + "example": "" + }, + { + "id": "LegacyFlagMetadata", + "name": "LegacyFlagMetadata", + "path": "posthog.types.LegacyFlagMetadata", + "properties": [ + { + "name": "payload", + "type": "Any", + "description": "Field: payload" + } + ], + "example": "" + }, + { + "id": "SendFeatureFlagsOptions", + "name": "SendFeatureFlagsOptions", + "path": "posthog.types.SendFeatureFlagsOptions", + "properties": [ + { + "name": "should_send", + "type": "bool", + "description": "Field: should_send" + }, + { + "name": "only_evaluate_locally", + "type": "Optional[bool]", + "description": "Field: only_evaluate_locally" + }, + { + "name": "person_properties", + "type": "Optional[dict[str, Any]]", + "description": "Field: person_properties" + }, + { + "name": "group_properties", + "type": "Optional[dict[str, dict[str, Any]]]", + "description": "Field: group_properties" + }, + { + "name": "flag_keys_filter", + "type": "Optional[list[str]]", + "description": "Field: flag_keys_filter" + } + ], + "example": "" + }, + { + "id": "OptionalCaptureArgs", + "name": "OptionalCaptureArgs", + "path": "posthog.args.OptionalCaptureArgs", + "properties": [ + { + "name": "distinct_id", + "type": "NotRequired[Union[Number, str, UUID, int, any]]", + "description": "Field: distinct_id" + }, + { + "name": "properties", + "type": "NotRequired[Optional[dict[str, Any]]]", + "description": "Field: properties" + }, + { + "name": "timestamp", + "type": "NotRequired[Union[datetime, str, any]]", + "description": "Field: timestamp" + }, + { + "name": "uuid", + "type": "NotRequired[Union[str, UUID, any]]", + "description": "Field: uuid" + }, + { + "name": "groups", + "type": "NotRequired[Optional[dict[str, str]]]", + "description": "Field: groups" + }, + { + "name": "flags", + "type": "NotRequired[Optional[ForwardRef('FeatureFlagEvaluations')]]", + "description": "Field: flags" + }, + { + "name": "send_feature_flags", + "type": "NotRequired[Union[bool, SendFeatureFlagsOptions, any]]", + "description": "Field: send_feature_flags" + }, + { + "name": "disable_geoip", + "type": "NotRequired[Optional[bool]]", + "description": "Field: disable_geoip" + }, + { + "name": "_property_allowlist", + "type": "NotRequired[Optional[frozenset[str]]]", + "description": "Field: _property_allowlist" + } + ], + "example": "" + }, + { + "id": "OptionalSetArgs", + "name": "OptionalSetArgs", + "path": "posthog.args.OptionalSetArgs", + "properties": [ + { + "name": "distinct_id", + "type": "NotRequired[Union[Number, str, UUID, int, any]]", + "description": "Field: distinct_id" + }, + { + "name": "properties", + "type": "NotRequired[Optional[dict[str, Any]]]", + "description": "Field: properties" + }, + { + "name": "timestamp", + "type": "NotRequired[Union[datetime, str, any]]", + "description": "Field: timestamp" + }, + { + "name": "uuid", + "type": "NotRequired[Union[str, UUID, any]]", + "description": "Field: uuid" + }, + { + "name": "disable_geoip", + "type": "NotRequired[Optional[bool]]", + "description": "Field: disable_geoip" + } + ], + "example": "" + }, + { + "id": "SendFeatureFlagsOptions", + "name": "SendFeatureFlagsOptions", + "path": "posthog.types.SendFeatureFlagsOptions", + "properties": [ + { + "name": "should_send", + "type": "bool", + "description": "Field: should_send" + }, + { + "name": "only_evaluate_locally", + "type": "Optional[bool]", + "description": "Field: only_evaluate_locally" + }, + { + "name": "person_properties", + "type": "Optional[dict[str, Any]]", + "description": "Field: person_properties" + }, + { + "name": "group_properties", + "type": "Optional[dict[str, dict[str, Any]]]", + "description": "Field: group_properties" + }, + { + "name": "flag_keys_filter", + "type": "Optional[list[str]]", + "description": "Field: flag_keys_filter" + } + ], + "example": "" + } + ], + "classes": [ + { + "id": "PostHog", + "title": "PostHog", + "description": "This is the SDK reference for the PostHog Python SDK. You can learn more about example usage in the [Python SDK documentation](/docs/libraries/python). You can also follow [Flask](/docs/libraries/flask) and [Django](/docs/libraries/django) guides to integrate PostHog into your project. For long-running applications, create one client during application startup and reuse it for the lifetime of the process. This keeps background queues predictable and makes shutdown flushing straightforward. Multiple clients are still supported for intentional multi-project or multi-host setups.", + "functions": [ + { + "id": "__init__", + "title": "Client", + "description": "Initialize a new PostHog client instance.", + "details": "", + "category": "Initialization", + "params": [ + { + "name": "project_api_key", + "description": "PostHog project API key/token.", + "isOptional": true, + "type": "str" + }, + { + "name": "host", + "description": "PostHog host. Defaults to the US ingestion endpoint when not set. App hosts such as ``https://us.posthog.com`` are mapped to the corresponding ingestion host.", + "isOptional": false, + "type": "any" + }, + { + "name": "debug", + "description": "Enable verbose SDK logging and re-raise errors from public API methods.", + "isOptional": false, + "type": "bool" + }, + { + "name": "max_queue_size", + "description": "Maximum number of events buffered before upload.", + "isOptional": false, + "type": "int" + }, + { + "name": "send", + "description": "If False, queueing succeeds but events are not sent.", + "isOptional": false, + "type": "bool" + }, + { + "name": "on_error", + "description": "Optional callback invoked by background consumers when an upload fails. Keep it short and non-blocking. Calling lifecycle methods directly is safe and deferred, but do not start another thread or task that calls ``flush()``, ``join()``, or ``shutdown()`` and then wait for it from the callback.", + "isOptional": false, + "type": "any" + }, + { + "name": "flush_at", + "description": "Number of queued events that triggers a batch upload.", + "isOptional": false, + "type": "int" + }, + { + "name": "flush_interval", + "description": "Maximum seconds a background consumer waits before flushing a partial batch.", + "isOptional": false, + "type": "float" + }, + { + "name": "gzip", + "description": "Whether to gzip event upload payloads.", + "isOptional": false, + "type": "bool" + }, + { + "name": "max_retries", + "description": "Number of upload retries. Values below 0 are treated as 0.", + "isOptional": false, + "type": "int" + }, + { + "name": "sync_mode", + "description": "If True, send each event synchronously instead of using background worker threads. This blocks the calling thread; in asyncio applications such as FastAPI, use ``AsyncPosthog`` instead.", + "isOptional": false, + "type": "bool" + }, + { + "name": "timeout", + "description": "HTTP request timeout in seconds for event uploads.", + "isOptional": false, + "type": "float" + }, + { + "name": "thread", + "description": "Number of background consumer threads.", + "isOptional": false, + "type": "int" + }, + { + "name": "poll_interval", + "description": "Seconds between local feature flag definition refreshes.", + "isOptional": false, + "type": "int" + }, + { + "name": "personal_api_key", + "description": "Deprecated alias for ``secret_key``. Still honored for backwards compatibility; prefer ``secret_key``, which also accepts a Project Secret API Key.", + "isOptional": false, + "type": "any" + }, + { + "name": "disabled", + "description": "If True, disable captures and API requests. Useful in tests.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable server-side GeoIP enrichment. Defaults to True.", + "isOptional": false, + "type": "bool" + }, + { + "name": "is_server", + "description": "Whether events are emitted from a server-side runtime. Defaults to True; set to False when using the SDK as a client/CLI so the device OS is attributed to the person normally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "historical_migration", + "description": "Mark events as historical migration imports.", + "isOptional": false, + "type": "bool" + }, + { + "name": "feature_flags_request_timeout_seconds", + "description": "Timeout in seconds for feature flag and remote config requests.", + "isOptional": false, + "type": "float" + }, + { + "name": "feature_flags_request_max_retries", + "description": "Number of retries for feature flag requests after network, transport, or timeout failures. Defaults to 1. Set to 0 to disable retries.", + "isOptional": false, + "type": "int" + }, + { + "name": "super_properties", + "description": "Properties merged into every captured event.", + "isOptional": false, + "type": "any" + }, + { + "name": "enable_exception_autocapture", + "description": "Automatically capture uncaught exceptions.", + "isOptional": false, + "type": "bool" + }, + { + "name": "log_captured_exceptions", + "description": "Also log exceptions captured by error tracking.", + "isOptional": false, + "type": "bool" + }, + { + "name": "project_root", + "description": "Root path used to determine in-app stack frames for captured exceptions. Defaults to the current working directory.", + "isOptional": false, + "type": "any" + }, + { + "name": "privacy_mode", + "description": "For AI observability, capture usage metadata without prompt inputs or outputs.", + "isOptional": false, + "type": "bool" + }, + { + "name": "before_send", + "description": "Optional callback that can modify or drop events before upload. Return ``None`` to drop an event.", + "isOptional": false, + "type": "any" + }, + { + "name": "flag_fallback_cache_url", + "description": "Optional feature flag fallback cache URL, such as ``memory://local/?ttl=300&size=10000`` or a Redis URL.", + "isOptional": false, + "type": "any" + }, + { + "name": "enable_local_evaluation", + "description": "Whether to poll feature flag definitions for local evaluation when a personal API key is configured.", + "isOptional": false, + "type": "bool" + }, + { + "name": "flag_definition_cache_provider", + "description": "Optional external cache provider for sharing feature flag definitions across workers.", + "isOptional": true, + "type": "FlagDefinitionCacheProvider" + }, + { + "name": "capture_exception_code_variables", + "description": "Capture local variable values on exception stack frames.", + "isOptional": false, + "type": "bool" + }, + { + "name": "code_variables_mask_patterns", + "description": "Variable-name patterns to mask when capturing code variables.", + "isOptional": false, + "type": "any" + }, + { + "name": "code_variables_ignore_patterns", + "description": "Variable-name patterns to omit when capturing code variables.", + "isOptional": false, + "type": "any" + }, + { + "name": "code_variables_mask_url_credentials", + "description": "Scrub credentials embedded in URLs/DSNs (e.g. ``user:pass@host``) from captured code variables, regardless of the surrounding variable name. Defaults to True.", + "isOptional": false, + "type": "any" + }, + { + "name": "code_variables_detect_secrets", + "description": "Last-resort entropy-based detection that redacts high-entropy secret-looking values (API keys, tokens, strong passwords) sitting in innocuously-named variables, after the name and URL checks. Skips structured ids (UUIDs, ObjectIds, hashes). Defaults to True.", + "isOptional": false, + "type": "any" + }, + { + "name": "in_app_modules", + "description": "Module/package prefixes treated as in-app frames in captured exceptions.", + "isOptional": false, + "type": "UnionType[list[str], any]" + }, + { + "name": "enable_exception_autocapture_rate_limiting", + "description": "Rate limit autocaptured exceptions client-side with a token bucket per exception type. Disabled by default.", + "isOptional": false, + "type": "bool" + }, + { + "name": "exception_autocapture_bucket_size", + "description": "Maximum burst of autocaptured exceptions allowed per exception type (token bucket size, clamped to 0-100).", + "isOptional": false, + "type": "int" + }, + { + "name": "exception_autocapture_refill_rate", + "description": "Tokens restored per refill interval for each exception type's bucket.", + "isOptional": false, + "type": "int" + }, + { + "name": "exception_autocapture_refill_interval_seconds", + "description": "Seconds between token refills for autocaptured exception rate limiting.", + "isOptional": false, + "type": "int" + }, + { + "name": "capture_mode", + "description": "Capture wire protocol to use. Defaults to ``CaptureMode.V0`` (legacy ``/batch/``). Set ``CaptureMode.V1`` (or pass the string ``\"v1\"``) to opt into ``/i/v1/analytics/events``. When omitted, the ``POSTHOG_CAPTURE_MODE`` env var is consulted, then ``V0``.", + "isOptional": false, + "type": "CaptureMode" + }, + { + "name": "capture_compression", + "description": "Request-body compression for capture-v1 uploads (ignored in V0, which uses ``gzip``). ``CaptureCompression.GZIP`` or ``DEFLATE`` (or the strings ``\"gzip\"``/``\"deflate\"``). When omitted, the ``POSTHOG_CAPTURE_COMPRESSION`` env var is consulted, then the legacy ``gzip`` flag, then no compression.", + "isOptional": false, + "type": "CaptureCompression" + }, + { + "name": "secret_key", + "description": "A Personal API Key or Project Secret API Key, used to authenticate local feature flag evaluation, remote config payloads, and decrypted flag payloads. Example:: posthog.Client(project_api_key, secret_key=\"phx_...\")", + "isOptional": false, + "type": "any" + }, + { + "name": "metrics", + "description": "", + "isOptional": true, + "type": "dict" + }, + { + "name": "enable_full_ai_capture", + "description": "Route PostHog AI wrapper events through the dedicated AI capture endpoint and capture full AI content: skips string truncation and passes media (base64/data URIs) through unredacted. ``privacy_mode`` always wins. Defaults to False.", + "isOptional": false, + "type": "bool" + }, + { + "name": "capture_trace_context", + "description": "When OpenTelemetry is installed and a valid span is active at capture time, add its trace and span IDs as ``$trace_id`` and ``$span_id`` properties to events captured with ``capture()`` and ``capture_ai()``, so they can be correlated with backend traces. Explicit ``$trace_id``/``$span_id`` values passed in ``properties`` win. Exception events (``capture_exception``) always attach these IDs regardless of this setting. Defaults to False.", + "isOptional": false, + "type": "bool" + }, + { + "name": "_use_ai_lane", + "description": "", + "isOptional": false, + "type": "bool" + }, + { + "name": "_enable_multimodal_capture", + "description": "", + "isOptional": false, + "type": "bool" + }, + { + "name": "traces", + "description": "Config dict for distributed tracing: ``service_name``, ``service_version``, ``environment``, ``resource_attributes``, ``flush_interval`` (5 s), ``max_queue_size`` (2048), ``max_export_batch_size`` (512), ``max_live_spans`` (10000), ``max_span_age`` (3600 s), ``max_attributes_per_span`` (128), ``max_events_per_span`` (128), ``max_attribute_value_length`` (8192). ``before_span_send`` is a callable, or a list run in order, that receives each finished span as a dict (``trace_id``, ``span_id`` and ``parent_span_id`` are read-only) and returns it, edited, or ``None`` to drop it; a hook that raises drops the span. Tracing is off until this is provided. Spans export on a background timer even with ``sync_mode``; serverless handlers should call ``flush()`` before returning. Defaults to None.", + "isOptional": true, + "type": "dict" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import Posthog\n\nposthog = Posthog('', host='')" + } + ] + }, + { + "id": "alias", + "title": "alias", + "description": "Create an alias between two distinct IDs.", + "details": "", + "category": "Identification", + "params": [ + { + "name": "previous_id", + "description": "The previous distinct ID. Required - the call is dropped with a warning if it is missing or empty.", + "isOptional": true, + "type": "Number" + }, + { + "name": "distinct_id", + "description": "The new distinct ID to alias to. Falls back to the context distinct ID; the call is dropped with a warning if neither is available.", + "isOptional": true, + "type": "str" + }, + { + "name": "timestamp", + "description": "The timestamp of the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC.", + "isOptional": false, + "type": "datetime" + }, + { + "name": "uuid", + "description": "A unique identifier for the event. If provided, it must be a valid UUID string or uuid.UUID instance; invalid values are ignored and replaced with a newly generated UUID.", + "isOptional": true, + "type": "str" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this event.", + "isOptional": true, + "type": "bool" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.alias(previous_id='distinct_id', distinct_id='alias_id')" + } + ] + }, + { + "id": "capture", + "title": "capture", + "description": "Captures an event manually. [Learn about capture best practices](https://posthog.com/docs/product-analytics/capture-events)", + "details": "", + "category": "Capture", + "params": [ + { + "name": "event", + "description": "The event name to capture.", + "isOptional": true, + "type": "str" + }, + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalCaptureArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Anonymous event", + "code": "# Anonymous event\nposthog.capture('some-anon-event')" + }, + { + "id": "example_2", + "name": "Context usage", + "code": "# Context usage\nfrom posthog import identify_context, new_context\nwith new_context():\n identify_context('distinct_id_of_the_user')\n posthog.capture('user_signed_up')\n posthog.capture('user_logged_in')\n posthog.capture('some-custom-action', distinct_id='distinct_id_of_the_user')" + }, + { + "id": "example_3", + "name": "Set event properties", + "code": "# Set event properties\nposthog.capture(\n \"user_signed_up\",\n distinct_id=\"distinct_id_of_the_user\",\n properties={\n \"login_type\": \"email\",\n \"is_free_trial\": \"true\"\n }\n)" + }, + { + "id": "example_4", + "name": "Page view event", + "code": "# Page view event\nposthog.capture('$pageview', distinct_id=\"distinct_id_of_the_user\", properties={'$current_url': 'https://example.com'})" + } + ] + }, + { + "id": "capture_ai", + "title": "capture_ai", + "description": "Capture an AI event on the dedicated AI capture endpoint. Beta: the signature is stable; operational limits (per-event size cap, batching, endpoint) may change without notice. Takes the same arguments and returns the same value as `capture()`: the event UUID, or None when the event was not admitted (disabled client, or dropped by `before_send`). The event is queued on an isolated AI lane with its own consumer pool and a higher per-event size cap, posting to the dedicated AI ingestion endpoint. The payload is sent as given \u2014 no redaction or truncation is applied here.", + "details": "", + "category": "Capture", + "params": [ + { + "name": "event", + "description": "", + "isOptional": true, + "type": "str" + }, + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalCaptureArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + } + }, + { + "id": "capture_exception", + "title": "capture_exception", + "description": "Capture an exception for error tracking. When OpenTelemetry is installed and a valid span is active, its trace and span IDs are added as ``$trace_id`` and ``$span_id`` event properties.", + "details": "", + "category": "Error Tracking", + "params": [ + { + "name": "exception", + "description": "The exception to capture.", + "isOptional": true, + "type": "BaseException" + }, + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalCaptureArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "try:\n # Some code that might fail\n pass\nexcept Exception as e:\n posthog.capture_exception(e, 'user_distinct_id', properties=additional_properties)" + } + ] + }, + { + "id": "evaluate_flags", + "title": "evaluate_flags", + "description": "Evaluate all feature flags for a user in a single call and return a :class:`FeatureFlagEvaluations` snapshot. Branch on ``.is_enabled()`` / ``.get_flag()`` and pass the same snapshot to :meth:`capture` via the ``flags`` option so events carry the exact flag values the code branched on. Prefer this over repeated ``get_feature_flag()`` calls and over ``capture(send_feature_flags=True)`` \u2014 it consolidates flag evaluation into a single ``/flags`` request per incoming request. Local evaluation is transparent: when the poller resolves a flag, the snapshot's ``$feature_flag_called`` events are tagged ``locally_evaluated=True`` and reason ``\"Evaluated locally\"``.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The user's distinct ID. If ``None``, falls back to the context distinct_id. If still unresolvable, returns an empty snapshot.", + "isOptional": false, + "type": "Number" + }, + { + "name": "groups", + "description": "Mapping of group type to group key.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "Person properties to use for evaluation.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "Group properties keyed by group type.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "If True, never fall back to remote evaluation \u2014 flags that can't be evaluated locally are simply omitted from the snapshot.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup.", + "isOptional": true, + "type": "bool" + }, + { + "name": "flag_keys", + "description": "Optional list that scopes local evaluation, the underlying ``/flags`` request, and the returned snapshot. When omitted or ``None``, all flags are evaluated. An empty list returns an empty snapshot without evaluating flags. A requested key absent from loaded local definitions is included in one remote fallback per ``evaluate_flags`` call unless ``only_evaluate_locally`` is True. If the server also does not know the key, it is omitted from the snapshot.", + "isOptional": true, + "type": "list[str]" + }, + { + "name": "device_id", + "description": "Optional device ID override. If not provided, falls back to the context device_id (which may be set via tracing headers). Used by experience-continuity flags to match users across distinct_id changes.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "FeatureFlagEvaluations" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "flags = posthog.evaluate_flags(\n \"user_123\",\n person_properties={\"plan\": \"enterprise\"},\n)\nif flags.is_enabled(\"new-dashboard\"):\n render_new_dashboard()\nposthog.capture(\"page_viewed\", distinct_id=\"user_123\", flags=flags)" + } + ] + }, + { + "id": "feature_enabled", + "title": "feature_enabled", + "description": "Check if a feature flag is enabled for a user.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "The feature flag key.", + "isOptional": true, + "type": "str" + }, + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to only evaluate locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "send_feature_flag_events", + "description": "Whether to send feature flag events.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[bool]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "is_my_flag_enabled = posthog.feature_enabled('flag-key', 'distinct_id_of_your_user')\nif is_my_flag_enabled:\n # Do something differently for this user\n # Optional: fetch the payload\n matched_flag_payload = posthog.get_feature_flag_payload('flag-key', 'distinct_id_of_your_user')" + } + ] + }, + { + "id": "feature_flag_definitions", + "title": "feature_flag_definitions", + "description": "Return feature flag definitions loaded for local evaluation. Returns: The currently loaded feature flag definitions, or ``None`` before local evaluation has loaded definitions.", + "details": "", + "category": "Feature flags", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "flush", + "title": "flush", + "description": "Attempt immediate delivery of queued events and ended spans, keeping the client reusable. For a client shared across requests or serverless invocations (including the module-level client), call ``flush()`` before a short-lived invocation returns. Use ``shutdown()`` instead only for final process cleanup or when disposing of a client that will not be reused. Flush does not stop the background workers or feature flag poller, and does not end open spans. Returning does not guarantee server receipt: pending work can remain after a timeout or span export failure, and failed events may be dropped by the consumers. Calls made directly from SDK callbacks such as ``on_error`` are deferred rather than blocking the worker that invoked the callback.", + "details": "", + "category": null, + "params": [ + { + "name": "timeout_seconds", + "description": "Seconds to wait for event queues to drain, shared across event lanes. Defaults to 10 seconds. Pass ``None`` to wait without a time limit; this does not guarantee successful delivery. Queued spans flush concurrently. The span exporter waits up to this many seconds for an in-flight flush, then uses a fresh drain budget once it acquires the lock. If the lock wait times out, the in-flight flush is left to finish. At least one span request is attempted after acquiring the lock, even with no budget left; retriable failures are retried after backoff while budget remains, with one last attempt at the deadline. Each span request is bounded by the client's ``timeout`` in seconds, and the last request is not cut short. This is therefore not a strict wall-clock limit for the whole call. With ``None``, a retriable span failure is left for a later flush rather than retried within this call.", + "isOptional": true, + "type": "float" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.capture('event_name', distinct_id='user_id')\n# Before a serverless invocation returns; keep the shared client open.\nposthog.flush(timeout_seconds=10)" + } + ] + }, + { + "id": "get_active_span", + "title": "get_active_span", + "description": "The span that is active in the current context, or ``None``. Alpha. Only entering a span (``with posthog.start_span(...) as span:``) makes it active; a span started manually is not. Use it to propagate the trace to the next service: ``span.traceparent()`` is the header value.", + "details": "", + "category": "Tracing", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[Span]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "span = posthog.get_active_span()\nif span is not None:\n headers[\"traceparent\"] = span.traceparent()" + } + ] + }, + { + "id": "get_all_flags", + "title": "get_all_flags", + "description": "Get all feature flags for a user.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to only evaluate locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "flag_keys_to_evaluate", + "description": "A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance.", + "isOptional": true, + "type": "list[str]" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[dict[str, Union[bool, str]]]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.get_all_flags('distinct_id_of_your_user')" + } + ] + }, + { + "id": "get_all_flags_and_payloads", + "title": "get_all_flags_and_payloads", + "description": "Get all feature flags and their payloads for a user.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to only evaluate locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "flag_keys_to_evaluate", + "description": "A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance.", + "isOptional": true, + "type": "list[str]" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "FlagsAndPayloads" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.get_all_flags_and_payloads('distinct_id_of_your_user')" + } + ] + }, + { + "id": "get_feature_flag", + "title": "get_feature_flag", + "description": "Get multivariate feature flag value for a user.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "The feature flag key.", + "isOptional": true, + "type": "str" + }, + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to only evaluate locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "send_feature_flag_events", + "description": "Whether to send feature flag events.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Union[bool, str, any]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "enabled_variant = posthog.get_feature_flag('flag-key', 'distinct_id_of_your_user')\nif enabled_variant == 'variant-key': # replace 'variant-key' with the key of your variant\n # Do something differently for this user\n # Optional: fetch the payload\n matched_flag_payload = posthog.get_feature_flag_payload('flag-key', 'distinct_id_of_your_user')" + } + ] + }, + { + "id": "get_feature_flag_evaluation_runtime", + "title": "get_feature_flag_evaluation_runtime", + "description": "Return where a locally loaded feature flag is meant to be evaluated.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "The feature flag key.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[FeatureFlagEvaluationRuntime]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import FeatureFlagEvaluationRuntime\n\nruntime = posthog.get_feature_flag_evaluation_runtime(\"my-flag\")\nif runtime is FeatureFlagEvaluationRuntime.SERVER:\n ..." + } + ] + }, + { + "id": "get_feature_flag_keys_by_evaluation_runtime", + "title": "get_feature_flag_keys_by_evaluation_runtime", + "description": "Return the keys of locally loaded flags that a runtime can evaluate. A flag set to ``FeatureFlagEvaluationRuntime.ALL`` suits either runtime, so it is returned for ``CLIENT`` and for ``SERVER``, and asking for ``ALL`` returns every loaded flag. Use this to decide which flags to hand to a browser when a backend serves flags to its own frontend.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "evaluation_runtime", + "description": "The runtime to match, as a ``FeatureFlagEvaluationRuntime`` or its string value.", + "isOptional": true, + "type": "FeatureFlagEvaluationRuntime" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "list[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import FeatureFlagEvaluationRuntime\n\nclient_keys = posthog.get_feature_flag_keys_by_evaluation_runtime(\n FeatureFlagEvaluationRuntime.CLIENT\n)" + } + ] + }, + { + "id": "get_feature_flag_payload", + "title": "get_feature_flag_payload", + "description": "Get the payload for a feature flag.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "The feature flag key.", + "isOptional": true, + "type": "str" + }, + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "match_value", + "description": "The specific flag value to get payload for.", + "isOptional": false, + "type": "bool" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to only evaluate locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "send_feature_flag_events", + "description": "Deprecated. Use get_feature_flag() instead if you need events.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[object]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "is_my_flag_enabled = posthog.feature_enabled('flag-key', 'distinct_id_of_your_user')\n\nif is_my_flag_enabled:\n # Do something differently for this user\n # Optional: fetch the payload\n matched_flag_payload = posthog.get_feature_flag_payload('flag-key', 'distinct_id_of_your_user')" + } + ] + }, + { + "id": "get_feature_flag_result", + "title": "get_feature_flag_result", + "description": "Get a FeatureFlagResult object which contains the flag result and payload for a key by evaluating locally or remotely depending on whether local evaluation is enabled and the flag can be locally evaluated. This also captures the `$feature_flag_called` event unless `send_feature_flag_events` is `False`.", + "details": "", + "category": null, + "params": [ + { + "name": "key", + "description": "The feature flag key.", + "isOptional": true, + "type": "str" + }, + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to only evaluate locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "send_feature_flag_events", + "description": "Whether to send feature flag events.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[FeatureFlagResult]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "flag_result = posthog.get_feature_flag_result('flag-key', 'distinct_id_of_your_user')\nif flag_result and flag_result.get_value() == 'variant-key':\n # Do something differently for this user\n # Optional: fetch the payload\n matched_flag_payload = flag_result.payload" + } + ] + }, + { + "id": "get_feature_flags_and_payloads", + "title": "get_feature_flags_and_payloads", + "description": "Get feature flags and payloads for a user.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "flag_keys_to_evaluate", + "description": "A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance.", + "isOptional": true, + "type": "list[str]" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "FlagsAndPayloads" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "result = posthog.get_feature_flags_and_payloads('')" + } + ] + }, + { + "id": "get_feature_payloads", + "title": "get_feature_payloads", + "description": "Get feature flag payloads for a user, preserving valid serialized JSON.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "flag_keys_to_evaluate", + "description": "A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance.", + "isOptional": true, + "type": "list[str]" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "dict[str, Optional[str]]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "payloads = posthog.get_feature_payloads('')" + } + ] + }, + { + "id": "get_feature_variants", + "title": "get_feature_variants", + "description": "Get feature flag variants for a user.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "flag_keys_to_evaluate", + "description": "A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance.", + "isOptional": true, + "type": "list[str]" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "dict[str, Union[bool, str]]" + } + }, + { + "id": "get_flags_decision", + "title": "get_flags_decision", + "description": "Get feature flags decision.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": false, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "flag_keys_to_evaluate", + "description": "A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance.", + "isOptional": true, + "type": "list[str]" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "FlagsResponse" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "decision = posthog.get_flags_decision('user123')" + } + ] + }, + { + "id": "get_remote_config_payload", + "title": "get_remote_config_payload", + "description": "Get the payload for a remote config feature flag.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "The remote config feature flag key.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "get_tags", + "title": "get_tags", + "description": "Get all tags from the current context. Returns: Dict of all tags in the current context.", + "details": "", + "category": "Contexts", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "dict[str, Any]" + } + }, + { + "id": "group_identify", + "title": "group_identify", + "description": "Identify a group and set its properties.", + "details": "", + "category": "Identification", + "params": [ + { + "name": "group_type", + "description": "The type of group (e.g., 'company', 'team'). Required - the call is dropped with a warning if it is missing or empty.", + "isOptional": true, + "type": "str" + }, + { + "name": "group_key", + "description": "The unique identifier for the group. Required - the call is dropped with a warning if it is missing or empty.", + "isOptional": true, + "type": "str" + }, + { + "name": "properties", + "description": "A dictionary of properties to set on the group.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "timestamp", + "description": "The timestamp of the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC.", + "isOptional": false, + "type": "datetime" + }, + { + "name": "uuid", + "description": "A unique identifier for the event. If provided, it must be a valid UUID string or uuid.UUID instance; invalid values are ignored and replaced with a newly generated UUID.", + "isOptional": false, + "type": "str" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this event.", + "isOptional": true, + "type": "bool" + }, + { + "name": "distinct_id", + "description": "The distinct ID of the user performing the action.", + "isOptional": false, + "type": "Number" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.group_identify('company', 'company_id_in_your_db', {\n 'name': 'Awesome Inc.',\n 'employees': 11\n})" + } + ] + }, + { + "id": "identify_context", + "title": "identify_context", + "description": "Identify the current context with a distinct ID.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "distinct_id", + "description": "The distinct ID to associate with the current context and its children.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + } + }, + { + "id": "join", + "title": "join", + "description": "Attempt to process queued events and end the consumer threads. Do not use directly, call `shutdown()` instead. Failed or undrainable events may be dropped and reported through logging or ``on_error``; returning does not guarantee server receipt. Lifecycle cleanup is attempted once, and cleanup failures are logged without retry.", + "details": "", + "category": null, + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.join()" + } + ] + }, + { + "id": "load_feature_flags", + "title": "load_feature_flags", + "description": "Load feature flags for local evaluation.", + "details": "", + "category": "Feature flags", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.load_feature_flags()" + } + ] + }, + { + "id": "new_context", + "title": "new_context", + "description": "Create a new context for managing shared state. Learn more about [contexts](/docs/libraries/python#contexts).", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "fresh", + "description": "Whether to create a fresh context that doesn't inherit from parent.", + "isOptional": false, + "type": "bool" + }, + { + "name": "capture_exceptions", + "description": "Whether to automatically capture exceptions in this context. If omitted, defaults to this client's exception autocapture setting.", + "isOptional": true, + "type": "bool" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "with client.new_context():\n client.identify_context('')\n client.capture('event_name')" + } + ] + }, + { + "id": "scoped", + "title": "scoped", + "description": "Decorator that creates a new context for the wrapped function using this client.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "fresh", + "description": "Whether to create a fresh context that doesn't inherit from parent.", + "isOptional": false, + "type": "bool" + }, + { + "name": "capture_exceptions", + "description": "Whether to automatically capture exceptions in this context. If omitted, defaults to this client's exception autocapture setting.", + "isOptional": true, + "type": "bool" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "set", + "title": "set", + "description": "Set properties on a person profile.", + "details": "", + "category": "Identification", + "params": [ + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalSetArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Set with distinct id", + "code": "# Set with distinct id\nposthog.set(distinct_id='user123', properties={'name': 'Max Hedgehog'})" + } + ] + }, + { + "id": "set_context_device_id", + "title": "set_context_device_id", + "description": "Set the device ID for the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "device_id", + "description": "The device ID to associate with the current context and its children.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + } + }, + { + "id": "set_context_session", + "title": "set_context_session", + "description": "Set the session ID for the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "session_id", + "description": "The session ID to associate with the current context and its children.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + } + }, + { + "id": "set_once", + "title": "set_once", + "description": "Set properties on a person profile only if they haven't been set before.", + "details": "", + "category": "Identification", + "params": [ + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalSetArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.set_once(distinct_id='user123', properties={'initial_signup_date': '2024-01-01'})" + } + ] + }, + { + "id": "shutdown", + "title": "shutdown", + "description": "Attempt final delivery and permanently shut down the client. Call this for final process cleanup or when disposing of a client that will not be reused. It closes capture lanes, stops background workers and the feature flag poller, and tears down integrations. Do not call it at the end of each request or serverless invocation when the client is shared; call ``flush()`` before returning instead. A shut-down client cannot be reused; create a new client if needed. Normally this method blocks until queued events have been attempted and cleanup finishes. Failed or undrainable events may be dropped and reported through logging or ``on_error``; returning does not guarantee server receipt. Event draining has no timeout. Queued spans get one final flush with up to 30 seconds waiting for an in-flight flush, then a fresh 30-second drain budget; the last HTTP request is not cut short. Any spans it cannot send are discarded with a warning, as are spans still open. Lifecycle cleanup is attempted once, and cleanup failures are logged without retry. When called directly from an SDK callback such as ``on_error``, shutdown is deferred to avoid blocking the worker that invoked the callback. If the callback must coordinate a blocking shutdown, have it signal an application-owned thread and return before that thread calls shutdown. Do not wait inside the callback for another thread or task that calls a lifecycle method.", + "details": "", + "category": null, + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.shutdown()" + } + ] + }, + { + "id": "start_span", + "title": "start_span", + "description": "Start a span for distributed tracing. Alpha. Returns a span handle. Use it as a context manager to make it the active span for the block and end it on exit (recording a raised exception on the way out); or call ``end()`` yourself for a span that cannot wrap a block. Spans started inside the block nest under it automatically. Always returns a usable handle, even when tracing is off, so calling code never branches.", + "details": "", + "category": "Tracing", + "params": [ + { + "name": "name", + "description": "A low-cardinality operation name, e.g. ``GET /users/:id``. Variable values belong in attributes, not the name.", + "isOptional": true, + "type": "str" + }, + { + "name": "kind", + "description": "``internal`` (default), ``server``, ``client``, ``producer`` or ``consumer``.", + "isOptional": true, + "type": "str" + }, + { + "name": "attributes", + "description": "Initial attributes.", + "isOptional": true, + "type": "Mapping[str, Any]" + }, + { + "name": "parent", + "description": "A span handle, or an inbound W3C ``traceparent`` header value to continue a remote trace. Defaults to the active span. A forked child starts with no active span; pass the parent span to continue a trace across a fork.", + "isOptional": false, + "type": "Span" + }, + { + "name": "tracestate", + "description": "The inbound ``tracestate`` header accompanying a ``traceparent`` string ``parent``; preserved and propagated.", + "isOptional": true, + "type": "str" + }, + { + "name": "start_time", + "description": "A ``datetime`` or epoch seconds, to backdate the span.", + "isOptional": false, + "type": "datetime" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Span" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog = Posthog(\"\", traces={\"service_name\": \"checkout-api\"})\n\nwith posthog.start_span(\"POST /checkout\", parent=request.headers.get(\"traceparent\")) as span:\n span.set_attribute(\"plan\", user.plan)\n with posthog.start_span(\"db.query\", kind=\"client\"):\n ...\n outgoing_headers = {\"traceparent\": span.traceparent()}" + } + ] + }, + { + "id": "tag", + "title": "tag", + "description": "Add a tag to the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "name", + "description": "The tag key.", + "isOptional": true, + "type": "str" + }, + { + "name": "value", + "description": "The tag value.", + "isOptional": true, + "type": "Any" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + } + } + ] + }, + { + "id": "PostHogModule", + "title": "PostHog Module Functions", + "description": "Global functions available in the PostHog module", + "functions": [ + { + "id": "alias", + "title": "alias", + "description": "Associate user behaviour before and after they e.g. register, login, or perform some other identifying action.", + "details": "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?\". Particularly useful for associating user behaviour before and after they e.g. register, login, or perform some other identifying action.", + "category": "Identification", + "params": [ + { + "name": "previous_id", + "description": "The unique ID of the user before", + "isOptional": true, + "type": "Number" + }, + { + "name": "distinct_id", + "description": "The current unique id", + "isOptional": true, + "type": "str" + }, + { + "name": "timestamp", + "description": "Optional timestamp for the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC.", + "isOptional": false, + "type": "datetime" + }, + { + "name": "uuid", + "description": "Optional UUID for the event", + "isOptional": true, + "type": "str" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup", + "isOptional": true, + "type": "bool" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Alias user", + "code": "# Alias user\nfrom posthog import alias\nalias(previous_id='distinct_id', distinct_id='alias_id')" + } + ] + }, + { + "id": "capture", + "title": "capture", + "description": "Capture anything a user does within your system.", + "details": "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. A capture call requires an event name to specify the event. We recommend using [verb] [noun], like `movie played` or `movie updated` to easily identify what your events mean later on. Capture takes a number of optional arguments, which are defined by the `OptionalCaptureArgs` type.", + "category": "Events", + "params": [ + { + "name": "event", + "description": "The event name to specify the event **kwargs: Optional arguments including:", + "isOptional": true, + "type": "str" + }, + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalCaptureArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Context and capture usage", + "code": "# Context and capture usage\nfrom posthog import new_context, identify_context, tag_context, capture\n# Enter a new context (e.g. a request/response cycle, an instance of a background job, etc)\nwith new_context():\n # Associate this context with some user, by distinct_id\n identify_context('some user')\n\n # Capture an event, associated with the context-level distinct ID ('some user')\n capture('movie started')\n\n # Capture an event associated with some other user (overriding the context-level distinct ID)\n capture('movie joined', distinct_id='some-other-user')\n\n # Capture an event with some properties\n capture('movie played', properties={'movie_id': '123', 'category': 'romcom'})\n\n # Capture an event with some properties\n capture('purchase', properties={'product_id': '123', 'category': 'romcom'})\n # Capture an event with some associated group\n capture('purchase', groups={'company': 'id:5'})\n\n # Adding a tag to the current context will cause it to appear on all subsequent events\n tag_context('some-tag', 'some-value')\n\n capture('another-event') # Will be captured with `'some-tag': 'some-value'` in the properties dict" + }, + { + "id": "example_2", + "name": "Set event properties", + "code": "# Set event properties\nfrom posthog import capture\ncapture(\n \"user_signed_up\",\n distinct_id=\"distinct_id_of_the_user\",\n properties={\n \"login_type\": \"email\",\n \"is_free_trial\": \"true\"\n }\n)" + } + ] + }, + { + "id": "capture_ai", + "title": "capture_ai", + "description": "Capture an AI event on the dedicated AI capture endpoint. Beta: the signature is stable; operational limits (per-event size cap, batching, endpoint) may change without notice. Takes the same arguments and returns the same value as `capture()`: the event UUID, or None when the event was not admitted (disabled client, or dropped by `before_send`). The event is delivered on an isolated queue with its own consumer pool and a higher per-event size cap, posting to the dedicated AI ingestion endpoint. The payload is sent as given \u2014 no redaction or truncation is applied here.", + "details": "", + "category": "Events", + "params": [ + { + "name": "event", + "description": "The event name, normally one of the `$ai_*` event names. **kwargs: Same optional arguments as `capture()`.", + "isOptional": true, + "type": "str" + }, + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalCaptureArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import capture_ai\n\nuuid = capture_ai(\n \"$ai_generation\",\n distinct_id=\"user_123\",\n properties={\"$ai_model\": \"gpt-5\"},\n)" + } + ] + }, + { + "id": "capture_exception", + "title": "capture_exception", + "description": "Capture exceptions that happen in your code.", + "details": "Capture exception is idempotent - if it is called twice with the same exception instance, only a occurrence will be tracked in posthog. This is because, generally, contexts will cause exceptions to be captured automatically. However, to ensure you track an exception, if you catch and do not re-raise it, capturing it manually is recommended, unless you are certain it will have crossed a context boundary (e.g. by existing a `with posthog.new_context():` block already). If the passed exception was raised and caught, the captured stack trace will consist of every frame between where the exception was raised and the point at which it is captured (the \"traceback\"). If the passed exception was never raised, e.g. if you call `posthog.capture_exception(ValueError(\"Some Error\"))`, the stack trace captured will be the full stack trace at the moment the exception was captured. Note that heavy use of contexts will lead to truncated stack traces, as the exception will be captured by the context entered most recently, 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. `capture_exception` takes the same set of optional arguments as `capture`.", + "category": "Events", + "params": [ + { + "name": "exception", + "description": "The exception to capture. If not provided, the current exception is captured via `sys.exc_info()` **kwargs: Optional capture arguments including distinct_id, properties, timestamp, uuid, groups, flags, send_feature_flags, and disable_geoip. Overriding reserved exception properties through ``properties`` is deprecated and will stop working in the next major version.", + "isOptional": false, + "type": "BaseException" + }, + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalCaptureArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Capture exception", + "code": "# Capture exception\nfrom posthog import capture_exception\ntry:\n risky_operation()\nexcept Exception as e:\n capture_exception(e)" + } + ] + }, + { + "id": "evaluate_flags", + "title": "evaluate_flags", + "description": "Evaluate all feature flags for a user in a single call and return a :class:`FeatureFlagEvaluations` snapshot. Branch on ``.is_enabled()`` / ``.get_flag()`` and pass the same snapshot to ``capture()`` via the ``flags`` option so events carry the exact flag values the code branched on. Prefer this over repeated ``get_feature_flag()`` calls and over ``capture(send_feature_flags=True)`` \u2014 it consolidates flag evaluation into a single ``/flags`` request per incoming request.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The user's distinct ID. If ``None``, falls back to the context distinct_id. If still unresolvable, returns an empty snapshot.", + "isOptional": false, + "type": "Number" + }, + { + "name": "groups", + "description": "Mapping of group type to group key.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "Person properties to use for evaluation.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "Group properties keyed by group type.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "If ``True``, never fall back to remote evaluation and omit flags that cannot be evaluated locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup.", + "isOptional": true, + "type": "bool" + }, + { + "name": "flag_keys", + "description": "Optional list that scopes local evaluation, the underlying ``/flags`` request, and the returned snapshot. When omitted or ``None``, all flags are evaluated. An empty list returns an empty snapshot without evaluating flags. A requested key absent from loaded local definitions is included in one remote fallback per ``evaluate_flags`` call unless ``only_evaluate_locally`` is ``True``. If the server also does not know the key, it is omitted from the snapshot.", + "isOptional": true, + "type": "list[str]" + }, + { + "name": "device_id", + "description": "Optional device ID override. If not provided, falls back to the context device_id (which may be set via tracing headers). Used by experience-continuity flags to match users across distinct_id changes.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "FeatureFlagEvaluations" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import evaluate_flags, capture\nflags = evaluate_flags(\"user_123\", person_properties={\"plan\": \"enterprise\"})\nif flags.is_enabled(\"new-dashboard\"):\n render_new_dashboard()\ncapture(\"page_viewed\", distinct_id=\"user_123\", flags=flags)" + } + ] + }, + { + "id": "feature_enabled", + "title": "feature_enabled", + "description": "Use feature flags to enable or disable features for users.", + "details": "You can call `posthog.load_feature_flags()` before to make sure you're not doing unexpected requests.", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "The feature flag key", + "isOptional": true, + "type": "str" + }, + { + "name": "distinct_id", + "description": "The user's distinct ID", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "Groups mapping", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "Person properties", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "Group properties", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to evaluate only locally", + "isOptional": false, + "type": "bool" + }, + { + "name": "send_feature_flag_events", + "description": "Whether to send feature flag events", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "Optional device ID override for experience-continuity flags", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[bool]" + }, + "examples": [ + { + "id": "example_1", + "name": "Boolean feature flag", + "code": "# Boolean feature flag\nfrom posthog import feature_enabled, get_feature_flag_payload\nis_my_flag_enabled = feature_enabled('flag-key', 'distinct_id_of_your_user')\nif is_my_flag_enabled:\n matched_flag_payload = get_feature_flag_payload('flag-key', 'distinct_id_of_your_user')" + } + ] + }, + { + "id": "feature_flag_definitions", + "title": "feature_flag_definitions", + "description": "Returns loaded feature flags.", + "details": "Returns loaded feature flags, if any. Helpful for debugging what flag information you have loaded.", + "category": "Feature flags", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import feature_flag_definitions\ndefinitions = feature_flag_definitions()" + } + ] + }, + { + "id": "flush", + "title": "flush", + "description": "Attempt immediate delivery of queued events and ended spans, keeping the global client reusable. Call ``flush()`` before a short-lived request or serverless invocation returns when using the shared module-level client. Use ``shutdown()`` only for final process cleanup or a client that will not be reused. Flush does not stop the background workers or feature flag poller, and does not end open spans. Returning does not guarantee server receipt: pending work can remain after a timeout or span export failure, and failed events may be dropped by consumers. Calls made directly from SDK callbacks such as ``on_error`` are deferred rather than blocking the worker that invoked the callback.", + "details": "", + "category": "Client management", + "params": [ + { + "name": "timeout_seconds", + "description": "Seconds to wait for event queues to drain, shared across event lanes. Defaults to 10 seconds. Pass ``None`` to wait without a time limit; this does not guarantee successful delivery. Queued spans flush concurrently, waiting up to this many seconds for an in-flight flush before starting a fresh drain budget. A lock wait timeout leaves the in-flight flush to finish. At least one span request is attempted after acquiring the lock, even with no budget left; retriable failures are retried after backoff while budget remains, with one last attempt at the deadline. The last span request is not cut short and is bounded by the client's ``timeout`` in seconds, so this is not a strict wall-clock limit for the whole call. With ``None``, a retriable span failure is left for a later flush rather than retried within this call.", + "isOptional": true, + "type": "float" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "import posthog\nposthog.capture('event_name', distinct_id='user_id')\n# Before a serverless invocation returns; keep the global client open.\nposthog.flush(timeout_seconds=10)" + } + ] + }, + { + "id": "get_active_span", + "title": "get_active_span", + "description": "The span that is active in the current context, or ``None``. Alpha. Only entering a span (``with posthog.start_span(...) as span:``) makes it active; a span started manually is not. Use it to propagate the trace to the next service: ``span.traceparent()`` is the header value.", + "details": "", + "category": "Tracing", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[Span]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "span = posthog.get_active_span()\nif span is not None:\n headers[\"traceparent\"] = span.traceparent()" + } + ] + }, + { + "id": "get_all_flags", + "title": "get_all_flags", + "description": "Get all flags for a given user.", + "details": "Flags are key-value pairs where the key is the flag key and the value is the flag variant, or True, or False.", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The user's distinct ID", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "Groups mapping", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "Person properties", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "Group properties", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to evaluate only locally", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "Optional device ID override for experience-continuity flags", + "isOptional": true, + "type": "str" + }, + { + "name": "flag_keys_to_evaluate", + "description": "Optional list of flag keys to evaluate (evaluates all if None)", + "isOptional": true, + "type": "list[str]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[dict[str, Union[bool, str]]]" + }, + "examples": [ + { + "id": "example_1", + "name": "All flags for user", + "code": "# All flags for user\nfrom posthog import get_all_flags\nget_all_flags('distinct_id_of_your_user')" + } + ] + }, + { + "id": "get_all_flags_and_payloads", + "title": "get_all_flags_and_payloads", + "description": "Get all feature flag values and payloads for a user.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The user's distinct ID.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "Mapping of group type to group key.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "Person properties to use for evaluation.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "Group properties keyed by group type.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to evaluate only locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup.", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "Optional device ID override for experience-continuity flags.", + "isOptional": true, + "type": "str" + }, + { + "name": "flag_keys_to_evaluate", + "description": "Optional list of flag keys to evaluate. Evaluates all flags when omitted.", + "isOptional": true, + "type": "list[str]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "FlagsAndPayloads" + } + }, + { + "id": "get_feature_flag", + "title": "get_feature_flag", + "description": "Get feature flag variant for users. Used with experiments.", + "details": "`groups` are a mapping from group type to group key. So, if you have a group type of \"organization\" and a group key of \"5\", you would pass groups={\"organization\": \"5\"}. `group_properties` take the format: { group_type_name: { group_properties } }. So, for example, if you have the group type \"organization\" and the group key \"5\", with the properties name, and employee count, you'll send these as: group_properties={\"organization\": {\"name\": \"PostHog\", \"employees\": 11}}.", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "The feature flag key", + "isOptional": true, + "type": "str" + }, + { + "name": "distinct_id", + "description": "The user's distinct ID", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "Groups mapping from group type to group key", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "Person properties", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "Group properties in format { group_type_name: { group_properties } }", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to evaluate only locally", + "isOptional": false, + "type": "bool" + }, + { + "name": "send_feature_flag_events", + "description": "Whether to send feature flag events", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "Optional device ID override for experience-continuity flags", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Union[bool, str, any]" + }, + "examples": [ + { + "id": "example_1", + "name": "Multivariate feature flag", + "code": "# Multivariate feature flag\nfrom posthog import get_feature_flag, get_feature_flag_payload\nenabled_variant = get_feature_flag('flag-key', 'distinct_id_of_your_user')\nif enabled_variant == 'variant-key':\n matched_flag_payload = get_feature_flag_payload('flag-key', 'distinct_id_of_your_user')" + } + ] + }, + { + "id": "get_feature_flag_evaluation_runtime", + "title": "get_feature_flag_evaluation_runtime", + "description": "Return where a locally loaded feature flag is meant to be evaluated.", + "details": "Reads the `evaluation_runtime` each flag definition carries, so no extra request is made. Returns `None` when local evaluation has not loaded a definition for this key. A definition that carries no runtime reports `FeatureFlagEvaluationRuntime.ALL`, the default PostHog applies.", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[FeatureFlagEvaluationRuntime]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import FeatureFlagEvaluationRuntime, get_feature_flag_evaluation_runtime\nruntime = get_feature_flag_evaluation_runtime(\"my-flag\")" + } + ] + }, + { + "id": "get_feature_flag_keys_by_evaluation_runtime", + "title": "get_feature_flag_keys_by_evaluation_runtime", + "description": "Return the keys of locally loaded flags that a runtime can evaluate.", + "details": "A flag set to `FeatureFlagEvaluationRuntime.ALL` suits either runtime, so it is returned for `CLIENT` and for `SERVER`. Use this to decide which flags to hand to a browser when a backend serves flags to its own frontend.", + "category": "Feature flags", + "params": [ + { + "name": "evaluation_runtime", + "description": "", + "isOptional": true, + "type": "FeatureFlagEvaluationRuntime" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "list[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import FeatureFlagEvaluationRuntime, get_feature_flag_keys_by_evaluation_runtime\nclient_keys = get_feature_flag_keys_by_evaluation_runtime(FeatureFlagEvaluationRuntime.CLIENT)" + } + ] + }, + { + "id": "get_feature_flag_payload", + "title": "get_feature_flag_payload", + "description": "Get the payload associated with a feature flag value. Deprecated for new code. Prefer ``evaluate_flags()`` and ``flags.get_flag_payload(key)`` so flag evaluation happens once per request.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "The feature flag key.", + "isOptional": true, + "type": "str" + }, + { + "name": "distinct_id", + "description": "The user's distinct ID.", + "isOptional": true, + "type": "Number" + }, + { + "name": "match_value", + "description": "Optional flag value to use when selecting a payload.", + "isOptional": false, + "type": "bool" + }, + { + "name": "groups", + "description": "Mapping of group type to group key.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "Person properties to use for evaluation.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "Group properties keyed by group type.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to evaluate only locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "send_feature_flag_events", + "description": "Whether to send a $feature_flag_called event.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup.", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "Optional device ID override for experience-continuity flags.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[object]" + } + }, + { + "id": "get_feature_flag_result", + "title": "get_feature_flag_result", + "description": "Get a FeatureFlagResult object which contains the flag result and payload. This method evaluates a feature flag and returns a FeatureFlagResult object containing: - enabled: Whether the flag is enabled - variant: The variant value if the flag has variants - payload: The payload associated with the flag (automatically deserialized from JSON) - key: The flag key - reason: Why the flag was enabled/disabled", + "details": "", + "category": null, + "params": [ + { + "name": "key", + "description": "The feature flag key.", + "isOptional": true, + "type": "str" + }, + { + "name": "distinct_id", + "description": "The user's distinct ID.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "Mapping of group type to group key.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "Person properties to use for evaluation.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "Group properties keyed by group type.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to evaluate only locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "send_feature_flag_events", + "description": "Whether to send a $feature_flag_called event.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup.", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "Optional device ID override for experience-continuity flags.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[FeatureFlagResult]" + } + }, + { + "id": "get_remote_config_payload", + "title": "get_remote_config_payload", + "description": "Get the payload for a remote config feature flag.", + "details": "", + "category": null, + "params": [ + { + "name": "key", + "description": "The key of the feature flag", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "get_tags", + "title": "get_tags", + "description": "Get all tags from the current context. Returns: Dict of all tags in the current context", + "details": "", + "category": "Contexts", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "dict[str, Any]" + } + }, + { + "id": "group_identify", + "title": "group_identify", + "description": "Set properties on a group.", + "details": "", + "category": "Identification", + "params": [ + { + "name": "group_type", + "description": "Type of your group. Required - the call is dropped with a warning if it is missing or empty.", + "isOptional": true, + "type": "str" + }, + { + "name": "group_key", + "description": "Unique identifier of the group. Required - the call is dropped with a warning if it is missing or empty.", + "isOptional": true, + "type": "str" + }, + { + "name": "properties", + "description": "Properties to set on the group", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "timestamp", + "description": "Optional timestamp for the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC.", + "isOptional": false, + "type": "datetime" + }, + { + "name": "uuid", + "description": "Optional UUID for the event", + "isOptional": true, + "type": "str" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup", + "isOptional": true, + "type": "bool" + }, + { + "name": "distinct_id", + "description": "Optional distinct ID of the user performing the action", + "isOptional": false, + "type": "Number" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Group identify", + "code": "# Group identify\nfrom posthog import group_identify\ngroup_identify('company', 'company_id_in_your_db', {\n 'name': 'Awesome Inc.',\n 'employees': 11\n})" + } + ] + }, + { + "id": "identify_context", + "title": "identify_context", + "description": "Identify the current context with a distinct ID.", + "details": "", + "category": "Identification", + "params": [ + { + "name": "distinct_id", + "description": "The distinct ID to associate with the current context and its children", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import identify_context\nidentify_context(\"user_123\")" + } + ] + }, + { + "id": "join", + "title": "join", + "description": "Attempt to process queued events and stop the client's background workers. Use `shutdown()` directly in most cases. Failed or undrainable events may be dropped and reported through logging or ``on_error``; returning does not guarantee server receipt. Lifecycle cleanup is attempted once, and cleanup failures are logged without retry.", + "details": "", + "category": "Client management", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import join\njoin()" + } + ] + }, + { + "id": "load_feature_flags", + "title": "load_feature_flags", + "description": "Load feature flag definitions from PostHog.", + "details": "", + "category": "Feature flags", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import load_feature_flags\nload_feature_flags()" + } + ] + }, + { + "id": "new_context", + "title": "new_context", + "description": "Create a new context scope that will be active for the duration of the with block.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "fresh", + "description": "Whether to start with a fresh context (default: False)", + "isOptional": false, + "type": "bool" + }, + { + "name": "capture_exceptions", + "description": "Whether to capture exceptions raised within the context. If omitted, defaults to the relevant client's exception autocapture setting.", + "isOptional": true, + "type": "bool" + }, + { + "name": "client", + "description": "Optional Posthog client instance to use for this context (default: None)", + "isOptional": true, + "type": "Client" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import new_context, tag, capture\nwith new_context():\n tag(\"request_id\", \"123\")\n capture(\"event_name\", properties={\"property\": \"value\"})" + } + ] + }, + { + "id": "scoped", + "title": "scoped", + "description": "Decorator that creates a new context for the function.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "fresh", + "description": "Whether to start with a fresh context (default: False)", + "isOptional": false, + "type": "bool" + }, + { + "name": "capture_exceptions", + "description": "Whether to capture and track exceptions with posthog error tracking. If omitted, defaults to the global exception autocapture setting.", + "isOptional": true, + "type": "bool" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import scoped, tag, capture\n@scoped()\ndef process_payment(payment_id):\n tag(\"payment_id\", payment_id)\n capture(\"payment_started\")" + } + ] + }, + { + "id": "set", + "title": "set", + "description": "Set properties on a user record.", + "details": "This will overwrite previous people property values. Generally operates similar to `capture`, with distinct_id being an optional argument, defaulting to the current context's distinct ID. If there is no context-level distinct ID, and no override distinct_id is passed, this function will do nothing. Context tags are folded into $set properties, so tagging the current context and then calling `set` will cause those tags to be set on the user (unlike capture, which causes them to just be set on the event).", + "category": "Identification", + "params": [ + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalSetArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Set person properties", + "code": "# Set person properties\nfrom posthog import set\nset(distinct_id='distinct_id', properties={'name': 'Max Hedgehog'})" + } + ] + }, + { + "id": "set_capture_exception_code_variables_context", + "title": "set_capture_exception_code_variables_context", + "description": "Override code-variable capture for exceptions in the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "enabled", + "description": "Whether exceptions captured in this context should include local variable values from stack frames.", + "isOptional": true, + "type": "bool" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "set_code_variables_detect_secrets_context", + "title": "set_code_variables_detect_secrets_context", + "description": "Whether to apply entropy-based secret detection as a last-resort redaction of high-entropy values (API keys, tokens, strong passwords) in captured code variables for the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "enabled", + "description": "", + "isOptional": true, + "type": "bool" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "set_code_variables_ignore_patterns_context", + "title": "set_code_variables_ignore_patterns_context", + "description": "Override code-variable ignore patterns for exceptions in the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "ignore_patterns", + "description": "Variable-name patterns that should be omitted entirely when code variables are captured.", + "isOptional": true, + "type": "list[str]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "set_code_variables_mask_patterns_context", + "title": "set_code_variables_mask_patterns_context", + "description": "Override code-variable mask patterns for exceptions in the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "mask_patterns", + "description": "Variable-name patterns whose values should be replaced with ``***`` when code variables are captured.", + "isOptional": true, + "type": "list[str]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "set_code_variables_mask_url_credentials_context", + "title": "set_code_variables_mask_url_credentials_context", + "description": "Whether to scrub credentials embedded in URLs/DSNs (e.g. user:pass@host) from captured code variables for the current context.", + "details": "", + "category": null, + "params": [ + { + "name": "enabled", + "description": "", + "isOptional": true, + "type": "bool" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "set_context_device_id", + "title": "set_context_device_id", + "description": "Set the device ID for the current context, associating all feature flag requests in this or child contexts with the given device ID.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "device_id", + "description": "The device ID to associate with the current context and its children", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import set_context_device_id\nset_context_device_id(\"device_123\")" + } + ] + }, + { + "id": "set_context_session", + "title": "set_context_session", + "description": "Set the session ID for the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "session_id", + "description": "The session ID to associate with the current context and its children", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import set_context_session\nset_context_session(\"session_123\")" + } + ] + }, + { + "id": "set_once", + "title": "set_once", + "description": "Set properties on a user record, only if they do not yet exist.", + "details": "This will not overwrite previous people property values, unlike `set`. Otherwise, operates in an identical manner to `set`.", + "category": "Identification", + "params": [ + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalSetArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Set property once", + "code": "# Set property once\nfrom posthog import set_once\nset_once(distinct_id='distinct_id', properties={'initial_url': '/blog'})" + } + ] + }, + { + "id": "setup", + "title": "setup", + "description": "Create or return the global PostHog client configured by module settings. Most applications should either instantiate ``Posthog`` directly or set ``posthog.api_key``/other module settings before calling top-level helpers. ``setup()`` is called automatically by global APIs such as ``capture()``. Returns: The global ``Client`` instance. If both ``api_key`` and ``project_api_key`` are missing or blank, the client is disabled and module-level calls become no-ops.", + "details": "", + "category": "Initialization", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Client" + } + }, + { + "id": "shutdown", + "title": "shutdown", + "description": "Attempt final delivery and permanently shut down the global client. Call this for final process cleanup, not at the end of each request or serverless invocation that shares the module-level client. Use ``flush()`` before returning from those invocations instead. Shutdown closes capture lanes, stops background workers and the feature flag poller, and tears down integrations. The global client is not recreated automatically after shutdown. This normally blocks until queued events have been attempted and cleanup finishes. Failed or undrainable events may be dropped and reported through logging or ``on_error``; returning does not guarantee server receipt. Event draining has no timeout. Queued spans get a final flush with up to 30 seconds waiting for an in-flight flush, then a fresh 30-second drain budget; the last HTTP request is not cut short. Unsent queued spans and still-open spans are discarded with a warning. Lifecycle cleanup is attempted once, and cleanup failures are logged without retry. Calls made directly from SDK callbacks such as ``on_error`` are deferred to avoid deadlocking the worker. If blocking completion is required, signal an application-owned thread, return from the callback, and call ``shutdown()`` from that thread. Do not wait inside a callback for another thread or task calling a lifecycle method.", + "details": "", + "category": "Client management", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import shutdown\nshutdown()" + } + ] + }, + { + "id": "start_span", + "title": "start_span", + "description": "Start a span for distributed tracing. Alpha. Returns a span handle. Use it as a context manager to make it the active span for the block and end it on exit (recording a raised exception on the way out); or call ``end()`` yourself for a span that cannot wrap a block. Spans started inside the block nest under it automatically. Always returns a usable handle, even when tracing is off, so calling code never branches.", + "details": "", + "category": "Tracing", + "params": [ + { + "name": "name", + "description": "A low-cardinality operation name, e.g. ``GET /users/:id``. Variable values belong in attributes, not the name.", + "isOptional": true, + "type": "str" + }, + { + "name": "kind", + "description": "``internal`` (default), ``server``, ``client``, ``producer`` or ``consumer``.", + "isOptional": true, + "type": "str" + }, + { + "name": "attributes", + "description": "Initial attributes.", + "isOptional": true, + "type": "Mapping[str, Any]" + }, + { + "name": "parent", + "description": "A span handle, or an inbound W3C ``traceparent`` header value to continue a remote trace. Defaults to the active span.", + "isOptional": false, + "type": "Span" + }, + { + "name": "tracestate", + "description": "The inbound ``tracestate`` header accompanying a ``traceparent`` string ``parent``; preserved and propagated.", + "isOptional": true, + "type": "str" + }, + { + "name": "start_time", + "description": "A ``datetime`` or epoch seconds, to backdate the span.", + "isOptional": false, + "type": "datetime" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Span" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "import posthog\nposthog.traces = {\"service_name\": \"checkout-api\"}\n\nwith posthog.start_span(\"POST /checkout\", parent=request.headers.get(\"traceparent\")) as span:\n span.set_attribute(\"plan\", user.plan)\n with posthog.start_span(\"db.query\", kind=\"client\"):\n ...\n outgoing_headers = {\"traceparent\": span.traceparent()}" + } + ] + }, + { + "id": "tag", + "title": "tag", + "description": "Add a tag to the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "name", + "description": "The tag key", + "isOptional": true, + "type": "str" + }, + { + "name": "value", + "description": "The tag value", + "isOptional": true, + "type": "Any" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import tag\ntag(\"user_id\", \"123\")" + } + ] + } + ] + } + ], + "categories": [ + "Initialization", + "Identification", + "Capture", + "Error Tracking", + "Feature flags", + "Tracing", + "Contexts", + "Events", + "Client management" + ] +} \ No newline at end of file diff --git a/references/posthog-python-references-latest.json b/references/posthog-python-references-latest.json index 70871253..66be86ee 100644 --- a/references/posthog-python-references-latest.json +++ b/references/posthog-python-references-latest.json @@ -2,7 +2,7 @@ "id": "posthog-python", "hogRef": "0.3", "info": { - "version": "7.77.0", + "version": "7.78.0", "id": "posthog-python", "title": "PostHog Python SDK", "description": "Integrate PostHog into any python application.", From c69e4592c046053c716b4c7fd374acac816350fa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 28 Oct 2026 13:23:58 -0400 Subject: [PATCH 34/60] chore(deps): bump the github-actions group with 3 updates (#1128) Bumps the github-actions group with 3 updates: [github/codeql-action/init](https://github.com/github/codeql-action), [github/codeql-action/analyze](https://github.com/github/codeql-action) and [PostHog/posthog-sdk-test-harness/.github/workflows/test-sdk-action.yml](https://github.com/posthog/posthog-sdk-test-harness). Updates `github/codeql-action/init` from 4.38.3 to 4.39.0 - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/24c54180a607b1449ed407dd24f251e4e9147c8d...6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee) Updates `github/codeql-action/analyze` from 4.38.3 to 4.39.0 - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/24c54180a607b1449ed407dd24f251e4e9147c8d...6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee) Updates `PostHog/posthog-sdk-test-harness/.github/workflows/test-sdk-action.yml` from 1.15.0 to 1.17.0 - [Release notes](https://github.com/posthog/posthog-sdk-test-harness/releases) - [Changelog](https://github.com/PostHog/posthog-sdk-test-harness/blob/main/CHANGELOG.md) - [Commits](https://github.com/posthog/posthog-sdk-test-harness/compare/64ffa6ab19166541880c077d349abe18a7e42395...8ca75c478d8a1b7d5be3f9b82b1a744b8ee630c3) --- updated-dependencies: - dependency-name: github/codeql-action/init dependency-version: 4.39.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: github-actions - dependency-name: github/codeql-action/analyze dependency-version: 4.39.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: github-actions - dependency-name: PostHog/posthog-sdk-test-harness/.github/workflows/test-sdk-action.yml dependency-version: 1.17.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: github-actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> (cherry picked from commit 7083998df0c26079e748f9279f92ca764ceb19ea) --- .github/workflows/codeql.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index fcaca264..ea90e4ce 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -30,7 +30,7 @@ jobs: uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Initialize CodeQL - uses: github/codeql-action/init@24c54180a607b1449ed407dd24f251e4e9147c8d # v4.38.3 + uses: github/codeql-action/init@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4.39.0 with: languages: ${{ matrix.language }} build-mode: ${{ matrix.build-mode }} @@ -40,6 +40,6 @@ jobs: trap-caching: false - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@24c54180a607b1449ed407dd24f251e4e9147c8d # v4.38.3 + uses: github/codeql-action/analyze@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4.39.0 with: category: '/language:${{matrix.language}}' From 4b0c41fdbdbe112adb536f6a8c2891c13bb8c6d7 Mon Sep 17 00:00:00 2001 From: "Gustavo H. Strassburger" Date: Wed, 28 Oct 2026 18:20:17 -0300 Subject: [PATCH 35/60] feat: report unresolved local flags on evaluate_flags snapshots (#1133) * feat: report unresolved local flags on evaluate_flags snapshots Implements the "Snapshot unresolved flag reporting" and "Warning for flags local evaluation never resolves" requirements of the evaluate-flags spec (PostHog/sdk-specs#85). - Add `FeatureFlagEvaluations.unresolved_flags`, with an `UnresolvedFlagReason` for each flag and the spec's fixed precedence. - Report `local_evaluation_inconclusive` instead of `flag_missing` when a snapshot read hits an unresolved flag. - Do not stop a condition at the first inconclusive filter, so a filter that does not match resolves the condition locally. - Check `active` before experience continuity, also for dependencies. - Log one warning per flag that local evaluation can never resolve, repeated only when its definition changes. Co-Authored-By: Claude Opus 5.5 Generated-By: PostHog Desktop Task-Id: 39793b31-809e-4689-a5d8-b4359cbb22c1 * fix: address review findings for unresolved local flags * test(flags): check flag warnings run outside the publication lock * fix(flags): fall back to the server when a flag dependency needs server data * fix(flags): keep server fallback when the definition reason lookup fails (cherry picked from commit bb2b9d6bddd250f6dcf03be565af8ba94694e5db) --- .sampo/changesets/unresolved-local-flags.md | 13 + posthog/__init__.py | 4 +- posthog/async_client.py | 2 + posthog/client.py | 157 ++++- posthog/feature_flag_evaluations.py | 32 +- posthog/feature_flags.py | 673 ++++++++++++++----- posthog/test/test_evaluate_flags.py | 699 ++++++++++++++++++++ posthog/test/test_feature_flags.py | 552 +++++++++++++++- posthog/types.py | 29 + references/public_api_snapshot.txt | 19 +- 10 files changed, 1997 insertions(+), 183 deletions(-) create mode 100644 .sampo/changesets/unresolved-local-flags.md diff --git a/.sampo/changesets/unresolved-local-flags.md b/.sampo/changesets/unresolved-local-flags.md new file mode 100644 index 00000000..135465dc --- /dev/null +++ b/.sampo/changesets/unresolved-local-flags.md @@ -0,0 +1,13 @@ +--- +pypi/posthog: minor +--- + +Report flags that local evaluation could not resolve on `evaluate_flags()` snapshots. `FeatureFlagEvaluations.unresolved_flags` maps each such flag to an `UnresolvedFlagReason` (`experience_continuity`, `unsupported_definition`, `unresolved_dependency` or `missing_context`), and reading one reports the `local_evaluation_inconclusive` error instead of `flag_missing`. When definitions load, log one warning per flag that local evaluation can never resolve (experience continuity, a static cohort, an unknown operator, a malformed value, or a missing or circular flag dependency), and again only when its definition changes. `AsyncClient` evaluates only remotely, so its snapshots have no unresolved flags. + +Behavior changes: + +- A condition with a filter that local evaluation can't check and a filter that does not match now resolves locally as no match. Before, such a flag fell back to a remote request, or stayed unresolved with `only_evaluate_locally=True`. +- An inactive flag with experience continuity enabled now resolves to `false` locally instead of falling back to a remote request. +- A flag that depends on a flag with experience continuity enabled is no longer evaluated locally. It falls back to a remote request, or stays unresolved with `only_evaluate_locally=True`. +- On `$feature_flag_called` events from an `evaluate_flags()` snapshot, `$feature_flag_error` is now `local_evaluation_inconclusive` instead of `flag_missing` for a flag that has a loaded definition but that local evaluation could not resolve. +- `get_all_flags()`, `get_all_flags_and_payloads()` and `evaluate_flags()` no longer log an error with a traceback when a flag needs the server (for example, a static cohort). They fall back to a remote request without logging, as `get_feature_flag()` already did. diff --git a/posthog/__init__.py b/posthog/__init__.py index d97d79fc..312e43f8 100644 --- a/posthog/__init__.py +++ b/posthog/__init__.py @@ -90,6 +90,7 @@ FeatureFlagEvaluationRuntime as FeatureFlagEvaluationRuntime, FlagValue as FlagValue, FlagsAndPayloads as FlagsAndPayloads, + UnresolvedFlagReason as UnresolvedFlagReason, ) from posthog.types import ( FeatureFlagResult as FeatureFlagResult, @@ -1175,7 +1176,8 @@ def evaluate_flags( person_properties: Person properties to use for evaluation. group_properties: Group properties keyed by group type. only_evaluate_locally: If ``True``, never fall back to remote evaluation and - omit flags that cannot be evaluated locally. + omit flags that cannot be evaluated locally. The snapshot's + ``unresolved_flags`` lists them with a reason. disable_geoip: Whether to disable GeoIP lookup. flag_keys: Optional list that scopes local evaluation, the underlying ``/flags`` request, and the returned snapshot. When omitted or ``None``, all flags are evaluated. diff --git a/posthog/async_client.py b/posthog/async_client.py index e9199438..766c56bb 100644 --- a/posthog/async_client.py +++ b/posthog/async_client.py @@ -1072,6 +1072,8 @@ async def evaluate_flags( The returned snapshot has synchronous in-memory accessors. Pass it to ``capture(flags=...)`` to attach the exact values used for branching. + This client does not evaluate flags locally, so the snapshot's + ``unresolved_flags`` is always empty. """ host = self._get_feature_flag_evaluations_host() if distinct_id is None: diff --git a/posthog/client.py b/posthog/client.py index 11aeee3d..ff91090a 100644 --- a/posthog/client.py +++ b/posthog/client.py @@ -13,7 +13,7 @@ import weakref from contextvars import ContextVar from datetime import datetime, timedelta, timezone -from typing import Any, Callable, Dict, List, Mapping, Optional, Union, cast +from typing import Any, Callable, Dict, List, Mapping, Optional, Tuple, Union, cast from uuid import UUID, uuid4 from typing_extensions import Unpack @@ -94,6 +94,7 @@ from posthog.feature_flags import ( InconclusiveMatchError, RequiresServerEvaluation, + _get_definition_unresolved_reason, match_feature_flag_properties, resolve_bucketing_value, ) @@ -127,6 +128,7 @@ FlagsResponse, FlagValue, SendFeatureFlagsOptions, + UnresolvedFlagReason, normalize_flags_response, to_flags_and_payloads, to_payloads, @@ -1021,6 +1023,11 @@ def __init__( self._flag_definition_cache_generation = 0 self._flag_definition_publication_lock = threading.RLock() self._flag_definition_cache_write_lock = threading.RLock() + # Flag key -> definition fingerprint of flags already warned about as never + # resolvable locally, so each warning fires once until the definition changes. + self._warned_unresolvable_flags: Dict[str, str] = {} + # Definitions fingerprint of the last scan for never-resolvable flags. + self._unresolvable_flags_fingerprint: Optional[str] = None self._flag_definition_cache_provider = flag_definition_cache_provider self._flag_definition_cache_provider_async_runner: Optional[ _BackgroundEventLoopRunner @@ -3467,6 +3474,101 @@ def _update_flag_state( return old_fingerprint return None + def _warn_unresolvable_flags(self) -> None: + """Log flags the published definitions can never resolve locally. + + Call it after publication, outside the publication lock: classifying every + flag walks its cohorts, and log handlers can be slow.""" + with self._flag_definition_publication_lock: + definitions_fingerprint = self._flag_definition_fingerprint + if definitions_fingerprint == self._unresolvable_flags_fingerprint: + return + snapshot = self._local_evaluation_snapshot() + + unresolvable = self._classify_unresolvable_flags(snapshot) + + with self._flag_definition_publication_lock: + # Newer definitions or another caller own the warning for this state. + if ( + definitions_fingerprint != self._flag_definition_fingerprint + or definitions_fingerprint == self._unresolvable_flags_fingerprint + ): + return + self._unresolvable_flags_fingerprint = definitions_fingerprint + new_keys_by_reason: Dict[UnresolvedFlagReason, List[str]] = {} + for key, (reason, fingerprint) in unresolvable.items(): + if self._warned_unresolvable_flags.get(key) != fingerprint: + new_keys_by_reason.setdefault(reason, []).append(key) + # Rebuilt from the current definitions, so a flag that is deleted or + # becomes resolvable is forgotten and warns again if it later becomes + # unresolvable. + self._warned_unresolvable_flags = { + key: fingerprint for key, (_, fingerprint) in unresolvable.items() + } + + self._log_unresolvable_flags(new_keys_by_reason) + + @staticmethod + def _classify_unresolvable_flags( + snapshot: _LocalEvaluationSnapshot, + ) -> Dict[str, Tuple[UnresolvedFlagReason, str]]: + """Map each never-resolvable flag key to its reason and a fingerprint of its + definition.""" + unresolvable: Dict[str, Tuple[UnresolvedFlagReason, str]] = {} + for flag in snapshot["flags"]: + try: + key = flag["key"] + reason = _get_definition_unresolved_reason( + flag, + snapshot["flags_by_key"], + snapshot["cohorts"], + snapshot["group_type_mapping"], + ) + except Exception: + # Evaluation reports a definition this malformed when the flag is used. + continue + if reason is None: + continue + fingerprint = _hashlib.sha256( + json.dumps([reason.value, flag], sort_keys=True, default=str).encode( + "utf-8" + ) + ).hexdigest() + unresolvable[str(key)] = (reason, fingerprint) + return unresolvable + + def _log_unresolvable_flags( + self, new_keys_by_reason: Dict[UnresolvedFlagReason, List[str]] + ) -> None: + experience_continuity_keys = new_keys_by_reason.get( + UnresolvedFlagReason.EXPERIENCE_CONTINUITY + ) + if experience_continuity_keys: + self.log.warning( + f"[FEATURE FLAGS] {len(experience_continuity_keys)} flag(s) have " + "experience continuity enabled and can't be evaluated locally " + f"(reason: {UnresolvedFlagReason.EXPERIENCE_CONTINUITY.value}): " + f"{', '.join(experience_continuity_keys)}. Evaluating them makes a " + "remote request, or leaves them unresolved with " + "only_evaluate_locally=True. To evaluate them locally, disable " + "experience continuity on these flags in PostHog." + ) + unsupported_definition_keys = new_keys_by_reason.get( + UnresolvedFlagReason.UNSUPPORTED_DEFINITION + ) + if unsupported_definition_keys: + self.log.warning( + f"[FEATURE FLAGS] {len(unsupported_definition_keys)} flag(s) use " + "something local evaluation doesn't support, such as a static " + "cohort, an unknown operator, a malformed value, or a missing or " + "circular flag dependency " + f"(reason: {UnresolvedFlagReason.UNSUPPORTED_DEFINITION.value}): " + f"{', '.join(unsupported_definition_keys)}. Evaluating them makes a " + "remote request when evaluation reaches that part, or leaves them " + "unresolved with only_evaluate_locally=True. To evaluate them " + "locally, change these flags in PostHog or upgrade posthog." + ) + def _local_evaluation_snapshot(self) -> _LocalEvaluationSnapshot: # Capture references together. Publication replaces these collections, so # recursive and multi-flag evaluations can finish on their original rules. @@ -3506,6 +3608,7 @@ def _load_feature_flags(self): "[FEATURE FLAGS] Using cached flag definitions from external cache" ) old_fingerprint = self._update_flag_state(cached_data) + self._warn_unresolvable_flags() if isinstance(self.flag_cache, RedisFlagCache): self.flag_cache._invalidate_snapshot(old_fingerprint) self._last_feature_flag_poll = datetime.now(tz=timezone.utc) @@ -3609,6 +3712,8 @@ def _fetch_feature_flags_from_api(self): self._flag_definition_published_generation = fetch_generation self._flag_definition_cache_generation = fetch_generation + self._warn_unresolvable_flags() + if cache_data_to_store and self._flag_definition_cache_provider: # Keep provider I/O out of the publication lock. The separate lock # preserves cache write order without delaying newer API fetches or @@ -3770,12 +3875,15 @@ def _compute_flag_locally( # Create evaluation cache for flag dependencies evaluation_cache: dict[str, Optional[FlagValue]] = {} - if feature_flag.get("ensure_experience_continuity", False): - raise InconclusiveMatchError("Flag has experience continuity enabled") - if not feature_flag.get("active"): return False + if feature_flag.get("ensure_experience_continuity", False): + raise InconclusiveMatchError( + "Flag has experience continuity enabled", + reason=UnresolvedFlagReason.EXPERIENCE_CONTINUITY, + ) + flag_filters = feature_flag.get("filters") or {} aggregation_group_type_index = flag_filters.get("aggregation_group_type_index") group_type_mapping = snapshot["group_type_mapping"] @@ -3788,7 +3896,10 @@ def _compute_flag_locally( f"[FEATURE FLAGS] Unknown group type index {aggregation_group_type_index} for feature flag {feature_flag['key']}" ) # failover to `/flags` - raise InconclusiveMatchError("Flag has unknown group type index") + raise InconclusiveMatchError( + "Flag has unknown group type index", + reason=UnresolvedFlagReason.UNSUPPORTED_DEFINITION, + ) if group_name not in groups: # Group flags are never enabled in `groups` aren't passed in @@ -3805,7 +3916,8 @@ def _compute_flag_locally( if group_name not in group_properties: raise InconclusiveMatchError( - f"Flag has no group properties for group '{group_name}'" + f"Flag has no group properties for group '{group_name}'", + reason=UnresolvedFlagReason.MISSING_CONTEXT, ) focused_group_properties = group_properties[group_name] group_key = groups[group_name] @@ -3824,9 +3936,23 @@ def _compute_flag_locally( group_properties=group_properties, ) else: - bucketing_value = resolve_bucketing_value( - feature_flag, distinct_id, device_id - ) + try: + bucketing_value = resolve_bucketing_value( + feature_flag, distinct_id, device_id + ) + except InconclusiveMatchError as e: + # No condition is checked without a bucketing value, so a cause in + # the definition also applies and outranks missing_context. + e.reason = ( + _get_definition_unresolved_reason( + feature_flag, + flags_by_key, + snapshot["cohorts"], + group_type_mapping, + ) + or e.reason + ) + raise return match_feature_flag_properties( feature_flag, distinct_id, @@ -4736,7 +4862,8 @@ def evaluate_flags( person_properties: Person properties to use for evaluation. group_properties: Group properties keyed by group type. only_evaluate_locally: If True, never fall back to remote evaluation — - flags that can't be evaluated locally are simply omitted from the snapshot. + flags that can't be evaluated locally are omitted from the snapshot and + listed with a reason in its ``unresolved_flags``. disable_geoip: Whether to disable GeoIP lookup. flag_keys: Optional list that scopes local evaluation, the underlying ``/flags`` request, and the returned snapshot. When omitted or ``None``, all @@ -4807,6 +4934,7 @@ def evaluate_flags( errors_while_computing = False quota_limited = False locally_evaluated_keys: set[str] = set() + unresolved: Dict[str, UnresolvedFlagReason] = {} # Try local evaluation first when the poller has loaded definitions. local_person_properties = self._person_properties_for_local_evaluation( distinct_id, person_properties @@ -4819,6 +4947,7 @@ def evaluate_flags( group_properties=group_properties, flag_keys_to_evaluate=flag_keys, device_id=device_id, + unresolved=unresolved, ) ) @@ -4929,6 +5058,9 @@ def evaluate_flags( errors_while_computing=errors_while_computing, quota_limited=quota_limited, minimal_flag_called_events=minimal_flag_called_events, + unresolved_flags={ + key: reason for key, reason in unresolved.items() if key not in records + }, ) _feature_flag_evaluations_host_cache: Optional[_FeatureFlagEvaluationsHost] = None @@ -4951,6 +5083,7 @@ def _get_all_flags_and_payloads_locally( warn_on_unknown_groups=False, flag_keys_to_evaluate: Optional[list[str]] = None, device_id: Optional[str] = None, + unresolved: Optional[Dict[str, UnresolvedFlagReason]] = None, ) -> tuple[FlagsAndPayloads, bool, _LocalEvaluationSnapshot]: person_properties = person_properties or {} group_properties = group_properties or {} @@ -4991,9 +5124,11 @@ def _get_all_flags_and_payloads_locally( ) if matched_payload is not None: payloads[flag["key"]] = matched_payload - except InconclusiveMatchError: + except (InconclusiveMatchError, RequiresServerEvaluation) as e: # No need to log this, since it's just telling us to fall back to `/flags` fallback_to_flags = True + if unresolved is not None: + unresolved[flag["key"]] = e.reason except Exception as e: self.log.exception( f"[FEATURE FLAGS] Error while computing variant and payload: {e}" diff --git a/posthog/feature_flag_evaluations.py b/posthog/feature_flag_evaluations.py index c260fbde..13d8c7df 100644 --- a/posthog/feature_flag_evaluations.py +++ b/posthog/feature_flag_evaluations.py @@ -6,9 +6,10 @@ """ from dataclasses import dataclass +from types import MappingProxyType from typing import Any, Callable, Dict, List, Mapping, Optional, Set, Union -from posthog.types import FlagValue +from posthog.types import FlagValue, UnresolvedFlagReason @dataclass(frozen=True) @@ -73,6 +74,7 @@ def __init__( quota_limited: bool = False, minimal_flag_called_events: bool = False, accessed: Optional[Set[str]] = None, + unresolved_flags: Optional[Mapping[str, UnresolvedFlagReason]] = None, ) -> None: """Internal — instances are created by the SDK via ``Client.evaluate_flags()``.""" self._host = host @@ -89,6 +91,9 @@ def __init__( # server response, not whatever the client-wide gate happens to be at send time. self._minimal_flag_called_events = minimal_flag_called_events self._accessed: Set[str] = set(accessed) if accessed is not None else set() + self._unresolved_flags: Dict[str, UnresolvedFlagReason] = dict( + unresolved_flags or {} + ) def is_enabled(self, key: str, default_value: bool = False) -> bool: """Return whether the flag is enabled. Fires ``$feature_flag_called`` on the @@ -165,6 +170,22 @@ def keys(self) -> List[str]: """Return the flag keys that are part of this evaluation.""" return list(self._flags.keys()) + @property + def unresolved_flags(self) -> Mapping[str, UnresolvedFlagReason]: + """Return a read-only mapping of flag key to the reason local evaluation + left it without a value. + + A flag is listed when it has a loaded local definition, was in the requested + scope, was inconclusive locally, and no remote value filled the gap. Listed + flags are not part of :attr:`keys`, and reading one reports the + ``local_evaluation_inconclusive`` error instead of ``flag_missing``. Reading + this mapping does not count as an access and does not fire any event. + Snapshots returned by :meth:`only` and :meth:`only_accessed`, and by + ``AsyncClient.evaluate_flags()``, which evaluates only remotely, carry no + entries. + """ + return MappingProxyType(self._unresolved_flags) + # --- Internal ------------------------------------------------------------- def _get_event_properties(self) -> Dict[str, Any]: @@ -256,14 +277,19 @@ def _record_access(self, key: str) -> None: # Build the comma-joined `$feature_flag_error` matching the single-flag path's # granularity: response-level errors (errors-while-computing, quota-limited) are - # combined with per-flag errors (flag-missing) so consumers can filter by type. + # combined with per-flag errors (flag-missing or local-evaluation-inconclusive) + # so consumers can filter by type. errors: List[str] = [] if self._errors_while_computing: errors.append("errors_while_computing_flags") if self._quota_limited: errors.append("quota_limited") if flag is None: - errors.append("flag_missing") + errors.append( + "local_evaluation_inconclusive" + if key in self._unresolved_flags + else "flag_missing" + ) if errors: properties["$feature_flag_error"] = ",".join(errors) diff --git a/posthog/feature_flags.py b/posthog/feature_flags.py index 66af600c..e0ffda65 100644 --- a/posthog/feature_flags.py +++ b/posthog/feature_flags.py @@ -12,7 +12,7 @@ from typing import NamedTuple, NoReturn, Optional from posthog import utils as utils -from posthog.types import FlagValue +from posthog.types import FlagValue, UnresolvedFlagReason from posthog.utils import convert_to_datetime_aware from posthog.utils import is_valid_regex as is_valid_regex @@ -96,19 +96,52 @@ class ConditionMatch(Enum): class InconclusiveMatchError(Exception): - pass + """ + Raised when local evaluation can't determine a flag's value. ``reason`` says + why, as reported by ``FeatureFlagEvaluations.unresolved_flags``. + """ + + reason: UnresolvedFlagReason = UnresolvedFlagReason.UNSUPPORTED_DEFINITION + + def __init__(self, *args, reason: Optional[UnresolvedFlagReason] = None): + super().__init__(*args) + if reason is not None: + self.reason = reason class RequiresServerEvaluation(Exception): """ Raised when feature flag evaluation requires server-side data that is not - available locally (e.g., static cohorts, experience continuity). + available locally (e.g., static cohorts). This error should propagate immediately to trigger API fallback, unlike InconclusiveMatchError which allows trying other conditions. """ - pass + reason: UnresolvedFlagReason = UnresolvedFlagReason.UNSUPPORTED_DEFINITION + + def __init__(self, *args, reason: Optional[UnresolvedFlagReason] = None): + super().__init__(*args) + if reason is not None: + self.reason = reason + + +# When several causes apply to one flag, the spec reports the first reason in this +# order, so every SDK picks the same reason whatever order it inspects filters in. +_UNRESOLVED_REASON_PRECEDENCE = ( + UnresolvedFlagReason.EXPERIENCE_CONTINUITY, + UnresolvedFlagReason.UNSUPPORTED_DEFINITION, + UnresolvedFlagReason.UNRESOLVED_DEPENDENCY, + UnresolvedFlagReason.MISSING_CONTEXT, +) + + +def _pick_unresolved_reason( + current: Optional[UnresolvedFlagReason], new: UnresolvedFlagReason +) -> UnresolvedFlagReason: + if current is None: + return new + return min(current, new, key=_UNRESOLVED_REASON_PRECEDENCE.index) # This function takes a bucketing value and a feature flag key and returns a float between 0 and 1. @@ -180,6 +213,20 @@ def variant_lookup_table(feature_flag): return lookup_table +def _flag_dependency_shape_problem(prop) -> Optional[str]: + """Describe why a flag dependency condition is malformed, or return None. + + Shared by local evaluation and the load-time classifier, so the two agree.""" + operator = prop.get("operator", "exact") + if operator != "flag_evaluates_to": + return f"invalid operator '{operator}'" + # Test `is None`, not truthiness: `False` is a valid expected value (the case + # flag dependencies exist for). + if not prop.get("key") or prop.get("value") is None: + return "missing key or value" + return None + + def evaluate_flag_dependency( property, flags_by_key, @@ -222,40 +269,62 @@ def evaluate_flag_dependency( if flags_by_key is None or evaluation_cache is None: # Cannot evaluate flag dependencies without required context raise InconclusiveMatchError( - f"Cannot evaluate flag dependency on '{property.get('key', 'unknown')}' without flags_by_key and evaluation_cache" + f"Cannot evaluate flag dependency on '{property.get('key', 'unknown')}' without flags_by_key and evaluation_cache", + reason=UnresolvedFlagReason.UNSUPPORTED_DEFINITION, ) # Validate the condition shape before walking the chain. A malformed shape # is a definitive no-match (return False), mirroring the server-side Rust # evaluator (match_flag_value_to_flag_filter). Raising InconclusiveMatchError # here would force a billable /flags network fallback on every evaluation of - # the affected flag. Test `is None`, not truthiness: `False` is a valid - # expected value (the case flag dependencies exist for). + # the affected flag. flag_key = property.get("key") expected_value = property.get("value") - operator = property.get("operator", "exact") - if operator != "flag_evaluates_to": - _warn_malformed_flag_dependency_once(flag_key, f"invalid operator '{operator}'") - return False - if not flag_key or expected_value is None: - _warn_malformed_flag_dependency_once(flag_key, "missing key or value") + shape_problem = _flag_dependency_shape_problem(property) + if shape_problem is not None: + _warn_malformed_flag_dependency_once(flag_key, shape_problem) return False # Check if dependency_chain is present - it should always be provided for flag dependencies if "dependency_chain" not in property: # Missing dependency_chain indicates malformed server data raise InconclusiveMatchError( - f"Flag dependency property for '{property.get('key', 'unknown')}' is missing required 'dependency_chain' field" + f"Flag dependency property for '{property.get('key', 'unknown')}' is missing required 'dependency_chain' field", + reason=UnresolvedFlagReason.UNSUPPORTED_DEFINITION, ) dependency_chain = property["dependency_chain"] + if not isinstance(dependency_chain, list): + raise InconclusiveMatchError( + f"Flag dependency property for '{flag_key}' has an invalid 'dependency_chain' field", + reason=UnresolvedFlagReason.UNSUPPORTED_DEFINITION, + ) + # Handle circular dependency (empty chain means circular) if len(dependency_chain) == 0: log.debug(f"Circular dependency detected for flag: {property.get('key')}") raise InconclusiveMatchError( - f"Circular dependency detected for flag '{property.get('key', 'unknown')}'" + f"Circular dependency detected for flag '{property.get('key', 'unknown')}'", + reason=UnresolvedFlagReason.UNSUPPORTED_DEFINITION, + ) + + # A dependency with no definition outranks one that is only inconclusive, so + # check the whole chain before evaluating any of it. + missing_dependency = next( + ( + key + for key in dependency_chain + if key not in evaluation_cache and not flags_by_key.get(key) + ), + None, + ) + if missing_dependency is not None: + evaluation_cache[missing_dependency] = None + raise InconclusiveMatchError( + f"Cannot evaluate flag dependency '{missing_dependency}' - flag not found in local flags", + reason=UnresolvedFlagReason.UNSUPPORTED_DEFINITION, ) # Evaluate and cache each flag in the chain; members already cached are @@ -264,19 +333,21 @@ def evaluate_flag_dependency( if dep_flag_key in evaluation_cache: continue - dep_flag = flags_by_key.get(dep_flag_key) - if not dep_flag: - # Missing flag dependency - cannot evaluate locally - evaluation_cache[dep_flag_key] = None - raise InconclusiveMatchError( - f"Cannot evaluate flag dependency '{dep_flag_key}' - flag not found in local flags" - ) + dep_flag = flags_by_key[dep_flag_key] - # Check if the flag is active (same check as in client._compute_flag_locally) + # Same order as client._compute_flag_locally: an inactive flag is false + # whatever its experience continuity setting. if not dep_flag.get("active"): evaluation_cache[dep_flag_key] = False continue + if dep_flag.get("ensure_experience_continuity"): + evaluation_cache[dep_flag_key] = None + raise InconclusiveMatchError( + f"Cannot evaluate flag dependency '{dep_flag_key}' - flag has experience continuity enabled", + reason=UnresolvedFlagReason.UNRESOLVED_DEPENDENCY, + ) + # Recursively evaluate the dependency try: dep_flag_filters = dep_flag.get("filters") or {} @@ -303,11 +374,20 @@ def evaluate_flag_dependency( bucketing_value=dep_bucketing_value, ) evaluation_cache[dep_flag_key] = dep_result + except RequiresServerEvaluation as e: + # The dependent flag needs the server too, so its later conditions must + # not resolve it locally. Nothing is cached, so another filter on this + # dependency also raises this error. + raise RequiresServerEvaluation( + f"Cannot evaluate flag dependency '{dep_flag_key}': {e}", + reason=UnresolvedFlagReason.UNRESOLVED_DEPENDENCY, + ) from e except InconclusiveMatchError as e: # If we can't evaluate a dependency, store None and propagate the error evaluation_cache[dep_flag_key] = None raise InconclusiveMatchError( - f"Cannot evaluate flag dependency '{dep_flag_key}': {e}" + f"Cannot evaluate flag dependency '{dep_flag_key}': {e}", + reason=UnresolvedFlagReason.UNRESOLVED_DEPENDENCY, ) from e # The condition matches iff the referenced flag's value matches the expected @@ -315,8 +395,16 @@ def evaluate_flag_dependency( # definitive False, which must be allowed to match `expected_value=False`. actual_value = evaluation_cache.get(flag_key) if actual_value is None: + # A referenced flag that has no loaded definition or is missing from the + # chain is a definition problem, not an unresolved dependency. + reason = ( + UnresolvedFlagReason.UNRESOLVED_DEPENDENCY + if flag_key in flags_by_key and flag_key in evaluation_cache + else UnresolvedFlagReason.UNSUPPORTED_DEFINITION + ) raise InconclusiveMatchError( - f"Flag dependency '{flag_key}' was inconclusive or not evaluated" + f"Flag dependency '{flag_key}' was inconclusive or not evaluated", + reason=reason, ) return matches_dependency_value(expected_value, actual_value) @@ -371,7 +459,8 @@ def resolve_bucketing_value(flag, distinct_id, device_id=None): if bucketing_identifier == "device_id": if not device_id: raise InconclusiveMatchError( - "Flag requires device_id for bucketing but none was provided" + "Flag requires device_id for bucketing but none was provided", + reason=UnresolvedFlagReason.MISSING_CONTEXT, ) return device_id return distinct_id @@ -412,7 +501,7 @@ def match_feature_flag_properties( flag_conditions = flag_filters.get("groups") or [] flag_aggregation = flag_filters.get("aggregation_group_type_index") early_exit_enabled = flag_filters.get("early_exit") - is_inconclusive = False + inconclusive_reason: Optional[UnresolvedFlagReason] = None cohort_properties = cohort_properties or {} groups = groups or {} group_properties = group_properties or {} @@ -443,7 +532,9 @@ def match_feature_flag_properties( ) continue if group_name not in group_properties: - is_inconclusive = True + inconclusive_reason = _pick_unresolved_reason( + inconclusive_reason, UnresolvedFlagReason.MISSING_CONTEXT + ) continue effective_properties = group_properties[group_name] effective_bucketing = groups[group_name] @@ -480,20 +571,32 @@ def match_feature_flag_properties( # The condition's property filters (if any) matched and only the rollout check # failed, so re-evaluating later groups can't change the outcome. If an earlier # condition was inconclusive, stop here but preserve that result for fallback. - if is_inconclusive: + if inconclusive_reason is not None: break return False - except RequiresServerEvaluation: - # Static cohort or other missing server-side data - must fallback to API + except RequiresServerEvaluation as e: + # Static cohort or other missing server-side data - must fallback to API. + # The loop stops here, so a filter it did not reach can still carry a + # higher-precedence reason in the flag's definition. + try: + definition_reason = _get_definition_unresolved_reason( + flag, flags_by_key or {}, cohort_properties, group_type_mapping + ) + except Exception: + # A malformed definition must not stop the fallback to the server. + definition_reason = None + if definition_reason is not None: + e.reason = _pick_unresolved_reason(e.reason, definition_reason) raise - except InconclusiveMatchError: + except InconclusiveMatchError as e: # Evaluation error (bad regex, invalid date, missing property, etc.) # Track that we had an inconclusive match, but try other conditions - is_inconclusive = True + inconclusive_reason = _pick_unresolved_reason(inconclusive_reason, e.reason) - if is_inconclusive: + if inconclusive_reason is not None: raise InconclusiveMatchError( - "Can't determine if feature flag is enabled or not with given properties" + "Can't determine if feature flag is enabled or not with given properties", + reason=inconclusive_reason, ) # We can only return False when either all conditions are False, or @@ -501,6 +604,43 @@ def match_feature_flag_properties( return False +def _match_condition_filter( + prop, + properties, + cohort_properties, + flags_by_key, + evaluation_cache, + distinct_id, + *, + device_id, + property_matching_version: int, +) -> bool: + property_type = prop.get("type") + if property_type == "cohort": + return match_cohort( + prop, + properties, + cohort_properties, + flags_by_key, + evaluation_cache, + distinct_id, + device_id=device_id, + property_matching_version=property_matching_version, + ) + if property_type == "flag": + return evaluate_flag_dependency( + prop, + flags_by_key, + evaluation_cache, + distinct_id, + properties, + cohort_properties, + device_id=device_id, + property_matching_version=property_matching_version, + ) + return match_property(prop, properties, property_matching_version) + + def is_condition_match( feature_flag, distinct_id, @@ -516,10 +656,14 @@ def is_condition_match( ) -> ConditionMatch: rollout_percentage = condition.get("rollout_percentage") if len(condition.get("properties") or []) > 0: + # Filters in a condition have no defined order, so an inconclusive filter does + # not stop the loop: a later filter can still rule the condition out or carry a + # higher-precedence reason. + inconclusive_reason: Optional[UnresolvedFlagReason] = None + server_evaluation_error: Optional[RequiresServerEvaluation] = None for prop in condition.get("properties"): - property_type = prop.get("type") - if property_type == "cohort": - matches = match_cohort( + try: + matches = _match_condition_filter( prop, properties, cohort_properties, @@ -529,22 +673,41 @@ def is_condition_match( device_id=device_id, property_matching_version=property_matching_version, ) - elif property_type == "flag": - matches = evaluate_flag_dependency( - prop, - flags_by_key, - evaluation_cache, - distinct_id, - properties, - cohort_properties, - device_id=device_id, - property_matching_version=property_matching_version, + except RequiresServerEvaluation as e: + # Fall back to the server only when this is the first filter that could + # not be checked, as when the loop stopped there. After another + # inconclusive filter, later conditions must still be able to resolve + # the flag locally. + if inconclusive_reason is None: + server_evaluation_error = e + inconclusive_reason = _pick_unresolved_reason( + inconclusive_reason, e.reason ) - else: - matches = match_property(prop, properties, property_matching_version) + continue + except InconclusiveMatchError as e: + inconclusive_reason = _pick_unresolved_reason( + inconclusive_reason, e.reason + ) + continue + except Exception: + # After an inconclusive filter, an unexpected error ends the condition + # as inconclusive, so later conditions can still resolve the flag. + if inconclusive_reason is None: + raise + break if not matches: return ConditionMatch.NO_MATCH + # Re-raise the original error type: match_feature_flag_properties stops at it + # and falls back to the server instead of trying later conditions. + if server_evaluation_error is not None: + raise server_evaluation_error + if inconclusive_reason is not None: + raise InconclusiveMatchError( + f"Can't determine if a condition of flag {feature_flag.get('key')} matches", + reason=inconclusive_reason, + ) + if rollout_percentage is None: return ConditionMatch.MATCH @@ -567,10 +730,14 @@ def is_condition_match( ) -def _format_json_float(value: float) -> str: +# The value helpers below convert both filter values and values from the evaluation +# call. ``reason`` names the owner of the value: UNSUPPORTED_DEFINITION for a filter +# value, MISSING_CONTEXT for a value the call passed. +def _format_json_float(value: float, reason: UnresolvedFlagReason) -> str: if not math.isfinite(value): raise InconclusiveMatchError( - "Non-finite property values cannot be represented by the flags service" + "Non-finite property values cannot be represented by the flags service", + reason=reason, ) representation = repr(value) @@ -586,7 +753,7 @@ def _format_json_float(value: float) -> str: return f"{mantissa}e{sign}{exponent}" -def _json_value_to_string(value) -> str: +def _json_value_to_string(value, reason: UnresolvedFlagReason) -> str: if value is None: return "null" if isinstance(value, bool): @@ -596,33 +763,37 @@ def _json_value_to_string(value) -> str: if isinstance(value, int): return str(value) if isinstance(value, float): - return _format_json_float(value) + return _format_json_float(value, reason) if isinstance(value, list): - return "[" + ",".join(_json_value_to_string(item) for item in value) + "]" + return ( + "[" + ",".join(_json_value_to_string(item, reason) for item in value) + "]" + ) if isinstance(value, dict): if not all(isinstance(key, str) for key in value): raise InconclusiveMatchError( - "Property object keys must be strings for local evaluation" + "Property object keys must be strings for local evaluation", + reason=reason, ) items = ( - f"{json.dumps(key, ensure_ascii=False)}:{_json_value_to_string(value[key])}" + f"{json.dumps(key, ensure_ascii=False)}:{_json_value_to_string(value[key], reason)}" for key in sorted(value) ) return "{" + ",".join(items) + "}" raise InconclusiveMatchError( - f"Property value of type {type(value).__name__} is not JSON-compatible" + f"Property value of type {type(value).__name__} is not JSON-compatible", + reason=reason, ) -def _value_to_string(value) -> str: +def _value_to_string(value, reason: UnresolvedFlagReason) -> str: if isinstance(value, str): return value - return _json_value_to_string(value) + return _json_value_to_string(value, reason) -def _ascii_lower(value) -> str: - return _value_to_string(value).translate(_ASCII_LOWER_TRANSLATION) +def _ascii_lower(value, reason: UnresolvedFlagReason) -> str: + return _value_to_string(value, reason).translate(_ASCII_LOWER_TRANSLATION) def _is_truthy_or_falsy_property_value(value) -> bool: @@ -686,7 +857,10 @@ def _compile_regex(pattern: str) -> Optional[_RegexMetadata]: index = 0 def defer() -> NoReturn: - raise InconclusiveMatchError("Regex requires server evaluation") + raise InconclusiveMatchError( + "Regex requires server evaluation", + reason=UnresolvedFlagReason.UNSUPPORTED_DEFINITION, + ) while index < len(pattern): character = pattern[index] @@ -824,7 +998,8 @@ def defer() -> NoReturn: return None except Exception as error: raise InconclusiveMatchError( - "Regex compilation requires server evaluation" + "Regex compilation requires server evaluation", + reason=UnresolvedFlagReason.UNSUPPORTED_DEFINITION, ) from error return _RegexMetadata( @@ -852,7 +1027,10 @@ def _match_regex(pattern: str, value: str) -> Optional[bool]: ) = metadata def defer() -> NoReturn: - raise InconclusiveMatchError("Regex requires server evaluation") + raise InconclusiveMatchError( + "Regex requires server evaluation", + reason=UnresolvedFlagReason.UNSUPPORTED_DEFINITION, + ) # Tightening $ can expose backtracking on a final LF, including repeated alternatives. if uses_end_anchor and uses_quantifier_or_alternation and value.endswith("\n"): @@ -873,12 +1051,122 @@ def defer() -> NoReturn: return regex.search(value) is not None except Exception as error: raise InconclusiveMatchError( - "Regex execution requires server evaluation" + "Regex execution requires server evaluation", + reason=UnresolvedFlagReason.UNSUPPORTED_DEFINITION, ) from error +_TEXT_OPERATORS = ( + "icontains", + "not_icontains", + "starts_with", + "not_starts_with", + "ends_with", + "not_ends_with", +) + + +def _parse_numeric_filter(value) -> float: + parsed_value = _parse_numeric_value(value) + if parsed_value is None: + raise InconclusiveMatchError( + "The numeric condition is not a finite number", + reason=UnresolvedFlagReason.UNSUPPORTED_DEFINITION, + ) + return parsed_value + + +def _parse_date_filter(value) -> datetime.datetime: + try: + parsed_date = relative_date_parse_for_feature_flag_matching(str(value)) + + if not parsed_date: + parsed_date = parse_datetime(str(value)) + parsed_date = convert_to_datetime_aware(parsed_date) + except Exception as e: + raise InconclusiveMatchError( + "The date set on the flag is not a valid format", + reason=UnresolvedFlagReason.UNSUPPORTED_DEFINITION, + ) from e + + if not parsed_date: + raise InconclusiveMatchError( + "The date set on the flag is not a valid format", + reason=UnresolvedFlagReason.UNSUPPORTED_DEFINITION, + ) + return parsed_date + + +@_lru_cache(maxsize=1024) +def _assert_date_filter_supported(value: str) -> None: + """Cache only valid filter values; lru_cache does not keep exceptions. Matching + still parses each time, so relative dates resolve against the current time.""" + _parse_date_filter(value) + + +def _parse_semver_filter(operator, value) -> tuple: + """Return the parsed version for a comparison operator, or the (lower, upper) + bounds for a range operator.""" + try: + if operator == "semver_tilde": + return _tilde_bounds(str(value)) + if operator == "semver_caret": + return _caret_bounds(str(value)) + if operator == "semver_wildcard": + return _wildcard_bounds(str(value)) + return parse_semver(value) + except (ValueError, TypeError): + raise InconclusiveMatchError( + f"Flag semver value '{value}' is not valid for operator {operator}", + reason=UnresolvedFlagReason.UNSUPPORTED_DEFINITION, + ) + + +def _assert_filter_supported(property) -> None: + """Raise an unsupported_definition error when local evaluation can't use a + property filter, whatever the call passes: an unknown operator, or a filter + value the operator can't parse.""" + operator = property.get("operator") or "exact" + value = property.get("value") + if operator not in PROPERTY_OPERATORS: + raise InconclusiveMatchError( + f"Unknown operator {operator}", + reason=UnresolvedFlagReason.UNSUPPORTED_DEFINITION, + ) + if operator in ("exact", "is_not"): + for item in value if isinstance(value, list) else [value]: + _value_to_string(item, UnresolvedFlagReason.UNSUPPORTED_DEFINITION) + elif operator in _TEXT_OPERATORS: + _value_to_string(value, UnresolvedFlagReason.UNSUPPORTED_DEFINITION) + elif operator in ("regex", "not_regex"): + pattern = str(value) + if pattern: + _compile_regex(pattern) + elif operator in NUMERIC_OPERATORS: + _parse_numeric_filter(value) + elif operator in DATE_OPERATORS: + _assert_date_filter_supported(str(value)) + elif operator in SEMVER_OPERATORS: + _parse_semver_filter(operator, value) + + def match_property( property, property_values, property_matching_version: int = 1 +) -> bool: + try: + return _match_property_value( + property, property_values, property_matching_version + ) + except InconclusiveMatchError as e: + # An unusable filter outranks missing context, so check the filter before + # blaming the context. + if e.reason == UnresolvedFlagReason.MISSING_CONTEXT: + _assert_filter_supported(property) + raise + + +def _match_property_value( + property, property_values, property_matching_version: int ) -> bool: # only looks for matches where key exists in override_property_values key = property.get("key") @@ -886,11 +1174,15 @@ def match_property( value = property.get("value") if operator not in PROPERTY_OPERATORS: - raise InconclusiveMatchError(f"Unknown operator {operator}") + raise InconclusiveMatchError( + f"Unknown operator {operator}", + reason=UnresolvedFlagReason.UNSUPPORTED_DEFINITION, + ) if key not in property_values: raise InconclusiveMatchError( - "can't match properties without a given property value" + "can't match properties without a given property value", + reason=UnresolvedFlagReason.MISSING_CONTEXT, ) if operator in ("is_set", "is_not_set"): @@ -899,9 +1191,7 @@ def match_property( override_value = property_values[key] if operator in NUMERIC_OPERATORS: - parsed_value = _parse_numeric_value(value) - if parsed_value is None: - raise InconclusiveMatchError("The numeric condition is not a finite number") + parsed_value = _parse_numeric_filter(value) parsed_override = _parse_numeric_value(override_value) if parsed_override is None: return False @@ -924,7 +1214,9 @@ def match_property( if operator in ("exact", "is_not"): def compute_exact_match(value, override_value): - override_string = _value_to_string(override_value).lower() + override_string = _value_to_string( + override_value, UnresolvedFlagReason.MISSING_CONTEXT + ).lower() # Empty filters retain recursive truthiness in both matching modes. if value == []: return _is_truthy_property_value(override_value) @@ -937,25 +1229,28 @@ def compute_exact_match(value, override_value): if isinstance(value, list): return override_string in [ - _value_to_string(candidate).lower() for candidate in value + _value_to_string( + candidate, UnresolvedFlagReason.UNSUPPORTED_DEFINITION + ).lower() + for candidate in value ] - return _value_to_string(value).lower() == override_string + return ( + _value_to_string( + value, UnresolvedFlagReason.UNSUPPORTED_DEFINITION + ).lower() + == override_string + ) if operator == "exact": return compute_exact_match(value, override_value) else: return not compute_exact_match(value, override_value) - if operator in ( - "icontains", - "not_icontains", - "starts_with", - "not_starts_with", - "ends_with", - "not_ends_with", - ): - property_string = _ascii_lower(override_value) - filter_string = _ascii_lower(value) + if operator in _TEXT_OPERATORS: + property_string = _ascii_lower( + override_value, UnresolvedFlagReason.MISSING_CONTEXT + ) + filter_string = _ascii_lower(value, UnresolvedFlagReason.UNSUPPORTED_DEFINITION) if operator in ("icontains", "not_icontains"): matched = filter_string in property_string @@ -975,22 +1270,8 @@ def compute_exact_match(value, override_value): return False return not regex_match if operator == "not_regex" else regex_match - if operator in ["is_date_before", "is_date_after"]: - try: - parsed_date = relative_date_parse_for_feature_flag_matching(str(value)) - - if not parsed_date: - parsed_date = parse_datetime(str(value)) - parsed_date = convert_to_datetime_aware(parsed_date) - except Exception as e: - raise InconclusiveMatchError( - "The date set on the flag is not a valid format" - ) from e - - if not parsed_date: - raise InconclusiveMatchError( - "The date set on the flag is not a valid format" - ) + if operator in DATE_OPERATORS: + parsed_date = _parse_date_filter(value) if isinstance(override_value, datetime.datetime): override_date = convert_to_datetime_aware(override_value) @@ -1012,71 +1293,51 @@ def compute_exact_match(value, override_value): else: return override_date > parsed_date except Exception: - raise InconclusiveMatchError("The date provided is not a valid format") + raise InconclusiveMatchError( + "The date provided is not a valid format", + reason=UnresolvedFlagReason.MISSING_CONTEXT, + ) else: raise InconclusiveMatchError( - "The date provided must be a string or date object" + "The date provided must be a string or date object", + reason=UnresolvedFlagReason.MISSING_CONTEXT, ) if operator in SEMVER_OPERATORS: + flag_semver = _parse_semver_filter(operator, value) try: override_parsed = parse_semver(override_value) except (ValueError, TypeError): raise InconclusiveMatchError( - f"Person property value '{override_value}' is not a valid semver" + f"Person property value '{override_value}' is not a valid semver", + reason=UnresolvedFlagReason.MISSING_CONTEXT, ) - if operator in SEMVER_COMPARISON_OPERATORS: - try: - flag_parsed = parse_semver(value) - except (ValueError, TypeError): - raise InconclusiveMatchError( - f"Flag semver value '{value}' is not a valid semver" - ) - - if operator == "semver_eq": - return override_parsed == flag_parsed - elif operator == "semver_neq": - return override_parsed != flag_parsed - elif operator == "semver_gt": - return override_parsed > flag_parsed - elif operator == "semver_gte": - return override_parsed >= flag_parsed - elif operator == "semver_lt": - return override_parsed < flag_parsed - elif operator == "semver_lte": - return override_parsed <= flag_parsed - - elif operator == "semver_tilde": - try: - lower, upper = _tilde_bounds(str(value)) - except (ValueError, TypeError): - raise InconclusiveMatchError( - f"Flag semver value '{value}' is not valid for tilde operator" - ) - return lower <= override_parsed < upper - - elif operator == "semver_caret": - try: - lower, upper = _caret_bounds(str(value)) - except (ValueError, TypeError): - raise InconclusiveMatchError( - f"Flag semver value '{value}' is not valid for caret operator" - ) - return lower <= override_parsed < upper - - elif operator == "semver_wildcard": - try: - lower, upper = _wildcard_bounds(str(value)) - except (ValueError, TypeError): - raise InconclusiveMatchError( - f"Flag semver value '{value}' is not valid for wildcard operator" - ) + if operator in SEMVER_RANGE_OPERATORS: + lower, upper = flag_semver return lower <= override_parsed < upper + if operator == "semver_eq": + return override_parsed == flag_semver + if operator == "semver_neq": + return override_parsed != flag_semver + if operator == "semver_gt": + return override_parsed > flag_semver + if operator == "semver_gte": + return override_parsed >= flag_semver + if operator == "semver_lt": + return override_parsed < flag_semver + if operator == "semver_lte": + return override_parsed <= flag_semver # Unreachable: all operators in PROPERTY_OPERATORS are handled above, # and unknown operators are rejected at the top of this function. - raise InconclusiveMatchError(f"Operator {operator} {_UNHANDLED_OPERATOR_MESSAGE}") + raise InconclusiveMatchError( + f"Operator {operator} {_UNHANDLED_OPERATOR_MESSAGE}", + reason=UnresolvedFlagReason.UNSUPPORTED_DEFINITION, + ) + + +_COHORT_OPERATORS = ("exact", "in", "not_in") def match_cohort( @@ -1104,6 +1365,15 @@ def match_cohort( f"cohort {cohort_id} not found in local cohorts - likely a static cohort that requires server evaluation" ) + # Check the operator before the members, so an unsupported operator outranks a + # member that is only missing context. + operator = property.get("operator") or "exact" + if operator not in _COHORT_OPERATORS: + raise InconclusiveMatchError( + f"Unsupported cohort operator: {operator}", + reason=UnresolvedFlagReason.UNSUPPORTED_DEFINITION, + ) + property_group = cohort_properties[cohort_id] matches = match_property_group( property_group, @@ -1115,13 +1385,12 @@ def match_cohort( device_id=device_id, property_matching_version=property_matching_version, ) + return not matches if operator == "not_in" else matches - operator = property.get("operator") or "exact" - if operator in ("exact", "in"): - return matches - if operator == "not_in": - return not matches - raise InconclusiveMatchError(f"Unsupported cohort operator: {operator}") + +def _is_nested_property_group(prop) -> bool: + """Whether a cohort group entry is itself a property group, not a filter.""" + return prop == {} or "values" in prop or prop.get("type") in ("AND", "OR") def match_property_group( @@ -1151,7 +1420,7 @@ def match_property_group( return True decisive_result = None - error_matching_locally = False + inconclusive_reason: Optional[UnresolvedFlagReason] = None for prop in properties: try: @@ -1160,7 +1429,7 @@ def match_property_group( "Cohort property group entry must be an object" ) - if prop == {} or "values" in prop or prop.get("type") in ("AND", "OR"): + if _is_nested_property_group(prop): matches = match_property_group( prop, property_values, @@ -1213,19 +1482,101 @@ def match_property_group( raise except InconclusiveMatchError as e: log.debug(f"Failed to compute property {prop} locally: {e}") - error_matching_locally = True + inconclusive_reason = _pick_unresolved_reason(inconclusive_reason, e.reason) if decisive_result is not None: return decisive_result - if error_matching_locally: + if inconclusive_reason is not None: raise InconclusiveMatchError( - "Can't match cohort without a given cohort property value" + "Can't match cohort without a given cohort property value", + reason=inconclusive_reason, ) # AND: every entry matched. OR: none matched. return is_and +def _get_definition_unresolved_reason( + flag, flags_by_key, cohort_properties, group_type_mapping +) -> Optional[UnresolvedFlagReason]: + """Return the unresolved reason that comes from the flag's definition alone, so + it is known when definitions load. Matches the reason local evaluation reports + for the same cause.""" + if not flag.get("active"): + return None + if flag.get("ensure_experience_continuity"): + return UnresolvedFlagReason.EXPERIENCE_CONTINUITY + flag_filters = flag.get("filters") or {} + aggregation = flag_filters.get("aggregation_group_type_index") + if aggregation is not None and not group_type_mapping.get(str(aggregation)): + return UnresolvedFlagReason.UNSUPPORTED_DEFINITION + for condition in flag_filters.get("groups") or []: + for prop in condition.get("properties") or []: + if _is_unsupported_property(prop, flags_by_key, cohort_properties, set()): + return UnresolvedFlagReason.UNSUPPORTED_DEFINITION + return None + + +def _is_unsupported_property( + prop, flags_by_key, cohort_properties, visited_cohort_ids +) -> bool: + property_type = prop.get("type") + if property_type == "flag": + # A malformed condition shape is a definitive local no-match, not a gap. + if _flag_dependency_shape_problem(prop) is not None: + return False + dependency_chain = prop.get("dependency_chain") + if not isinstance(dependency_chain, list) or not dependency_chain: + return True + return any(not flags_by_key.get(key) for key in dependency_chain) + if property_type != "cohort": + try: + _assert_filter_supported(prop) + except InconclusiveMatchError: + return True + return False + cohort_id = str(prop.get("value")) + if cohort_id not in cohort_properties: + return True + if (prop.get("operator") or "exact") not in _COHORT_OPERATORS: + return True + if cohort_id in visited_cohort_ids: + return False + visited_cohort_ids.add(cohort_id) + return _is_unsupported_property_group( + cohort_properties[cohort_id], + flags_by_key, + cohort_properties, + visited_cohort_ids, + ) + + +def _is_unsupported_property_group( + property_group, flags_by_key, cohort_properties, visited_cohort_ids +) -> bool: + if property_group == {}: + return False + if not isinstance(property_group, dict): + return True + values = property_group.get("values") + if property_group.get("type") not in ("AND", "OR") or not isinstance(values, list): + return True + for prop in values: + if not isinstance(prop, dict): + return True + if _is_nested_property_group(prop): + unsupported = _is_unsupported_property_group( + prop, flags_by_key, cohort_properties, visited_cohort_ids + ) + else: + unsupported = _is_unsupported_property( + prop, flags_by_key, cohort_properties, visited_cohort_ids + ) + if unsupported: + return True + return False + + def parse_datetime(value: str) -> datetime.datetime: text = value.strip() if text.endswith("Z"): diff --git a/posthog/test/test_evaluate_flags.py b/posthog/test/test_evaluate_flags.py index ac93d0dc..cd3f34a9 100644 --- a/posthog/test/test_evaluate_flags.py +++ b/posthog/test/test_evaluate_flags.py @@ -1,3 +1,4 @@ +import logging import unittest import warnings from unittest import mock @@ -6,7 +7,9 @@ from posthog.client import Client from posthog.feature_flag_evaluations import FeatureFlagEvaluations +from posthog.request import GetResponse from posthog.test.test_utils import FAKE_TEST_API_KEY +from posthog.types import UnresolvedFlagReason def _flags_response_fixture(): @@ -552,6 +555,702 @@ def test_context_device_id_is_used_for_local_evaluation( patch_flags.assert_not_called() +def _always_on_flag(key, **extra): + return { + "id": 1, + "key": key, + "active": True, + "filters": {"groups": [{"properties": [], "rollout_percentage": 100}]}, + **extra, + } + + +def _flag_with(key, *properties, **extra): + flag = _always_on_flag(key, **extra) + flag["filters"]["groups"][0]["properties"] = list(properties) + return flag + + +def _depends_on(key): + return { + "key": key, + "type": "flag", + "value": True, + "operator": "flag_evaluates_to", + "dependency_chain": [key], + } + + +_PLAN_IS_PRO = {"key": "plan", "operator": "exact", "value": "pro", "type": "person"} +_STATIC_COHORT = {"key": "id", "type": "cohort", "value": 42} + + +def _flag_called_properties(patch_capture): + return { + c[1]["properties"]["$feature_flag"]: c[1]["properties"] + for c in patch_capture.call_args_list + if c[0] and c[0][0] == "$feature_flag_called" + } + + +class TestEvaluateFlagsUnresolved(unittest.TestCase): + """Mirrors the ``@unresolved_flags_capable`` scenarios of the evaluate-flags spec.""" + + def setUp(self): + self.client = Client(FAKE_TEST_API_KEY, secret_key="test") + self.client.feature_flags = [ + _always_on_flag("beta-ui"), + _always_on_flag("checkout", ensure_experience_continuity=True), + ] + + def tearDown(self): + self.client.shutdown() + + @mock.patch("posthog.client.flags") + @mock.patch.object(Client, "capture") + def test_local_only_reports_experience_continuity_flag( + self, patch_capture, patch_flags + ): + flags = self.client.evaluate_flags("user-123", only_evaluate_locally=True) + + self.assertEqual(flags.keys, ["beta-ui"]) + self.assertEqual( + dict(flags.unresolved_flags), + {"checkout": UnresolvedFlagReason.EXPERIENCE_CONTINUITY}, + ) + self.assertEqual(flags.unresolved_flags["checkout"], "experience_continuity") + patch_flags.assert_not_called() + self.assertEqual(_flag_called_properties(patch_capture), {}) + self.assertEqual(flags.only_accessed().keys, []) + self.assertEqual(dict(flags.only(["beta-ui", "checkout"]).unresolved_flags), {}) + self.assertIs(flags.get_flag("beta-ui"), True) + + @mock.patch("posthog.client.flags") + def test_unresolved_flags_is_read_only(self, _patch_flags): + flags = self.client.evaluate_flags("user-123", only_evaluate_locally=True) + + with self.assertRaises(TypeError): + flags.unresolved_flags["beta-ui"] = UnresolvedFlagReason.MISSING_CONTEXT # type: ignore[index] + + @parameterized.expand( + [ + ( + "missing_person_property", + _flag_with("checkout", _PLAN_IS_PRO), + {}, + UnresolvedFlagReason.MISSING_CONTEXT, + ), + ( + "device_bucketed_without_device_id", + _always_on_flag("checkout", bucketing_identifier="device_id"), + {}, + UnresolvedFlagReason.MISSING_CONTEXT, + ), + ( + "unrecognized_operator", + _flag_with( + "checkout", + { + "key": "plan", + "operator": "bogus", + "value": "x", + "type": "person", + }, + ), + {"person_properties": {"plan": "enterprise"}}, + UnresolvedFlagReason.UNSUPPORTED_DEFINITION, + ), + ( + "static_cohort", + _flag_with("checkout", _STATIC_COHORT), + {}, + UnresolvedFlagReason.UNSUPPORTED_DEFINITION, + ), + ( + "static_cohort_and_missing_property", + _flag_with("checkout", _STATIC_COHORT, _PLAN_IS_PRO), + {}, + UnresolvedFlagReason.UNSUPPORTED_DEFINITION, + ), + ( + "missing_property_and_static_cohort", + _flag_with("checkout", _PLAN_IS_PRO, _STATIC_COHORT), + {}, + UnresolvedFlagReason.UNSUPPORTED_DEFINITION, + ), + ( + "device_bucketed_with_static_cohort", + _flag_with( + "checkout", _STATIC_COHORT, bucketing_identifier="device_id" + ), + {}, + UnresolvedFlagReason.UNSUPPORTED_DEFINITION, + ), + ] + ) + @mock.patch("posthog.client.flags") + def test_each_inconclusive_cause_reports_its_reason( + self, _name, checkout, call_kwargs, reason, patch_flags + ): + self.client.feature_flags = [ + {**checkout, "ensure_experience_continuity": False} + ] + + flags = self.client.evaluate_flags( + "user-123", only_evaluate_locally=True, **call_kwargs + ) + + self.assertNotIn("checkout", flags.keys) + self.assertEqual(dict(flags.unresolved_flags), {"checkout": reason}) + patch_flags.assert_not_called() + + @mock.patch("posthog.client.flags") + def test_flag_blocked_by_unresolved_dependency_reports_the_dependency( + self, _patch_flags + ): + self.client.feature_flags = [ + _always_on_flag("checkout", ensure_experience_continuity=True), + _flag_with("beta-ui", _depends_on("checkout")), + ] + + flags = self.client.evaluate_flags( + "user-123", flag_keys=["beta-ui"], only_evaluate_locally=True + ) + + self.assertNotIn("beta-ui", flags.keys) + self.assertEqual( + dict(flags.unresolved_flags), + {"beta-ui": UnresolvedFlagReason.UNRESOLVED_DEPENDENCY}, + ) + + @mock.patch("posthog.client.flags") + def test_dependency_in_scope_carries_its_own_reason(self, _patch_flags): + self.client.feature_flags = [ + _flag_with("checkout", _PLAN_IS_PRO), + _flag_with("beta-ui", _depends_on("checkout")), + ] + + flags = self.client.evaluate_flags("user-123", only_evaluate_locally=True) + + self.assertEqual( + dict(flags.unresolved_flags), + { + "checkout": UnresolvedFlagReason.MISSING_CONTEXT, + "beta-ui": UnresolvedFlagReason.UNRESOLVED_DEPENDENCY, + }, + ) + + @mock.patch("posthog.client.flags") + def test_flag_depending_on_flag_without_definition_is_unsupported_definition( + self, _patch_flags + ): + self.client.feature_flags = [_flag_with("beta-ui", _depends_on("checkout"))] + + flags = self.client.evaluate_flags( + "user-123", flag_keys=["beta-ui"], only_evaluate_locally=True + ) + + self.assertNotIn("beta-ui", flags.keys) + self.assertEqual( + dict(flags.unresolved_flags), + {"beta-ui": UnresolvedFlagReason.UNSUPPORTED_DEFINITION}, + ) + + @staticmethod + def _target_depending_on(dep_property): + """``target`` is ``a`` when ``dep`` is true, and ``b`` for everyone else.""" + target = _always_on_flag("target") + target["filters"] = { + "groups": [ + { + "properties": [_depends_on("dep")], + "rollout_percentage": 100, + "variant": "a", + }, + {"properties": [], "rollout_percentage": 100, "variant": "b"}, + ], + "multivariate": { + "variants": [ + {"key": "a", "rollout_percentage": 50}, + {"key": "b", "rollout_percentage": 50}, + ] + }, + } + return [_flag_with("dep", dep_property), target] + + @parameterized.expand([("all_flags", None), ("target_only", ["target"])]) + @mock.patch("posthog.client.flags") + def test_dependency_that_needs_the_server_falls_back_to_the_server( + self, _name, flag_keys, patch_flags + ): + self.client.feature_flags = self._target_depending_on( + {"key": "id", "type": "cohort", "value": 999} + ) + patch_flags.return_value = { + "flags": {"target": {"enabled": True, "variant": "a"}} + } + + flags = self.client.evaluate_flags("user-123", flag_keys=flag_keys) + + self.assertEqual(flags.get_flag("target"), "a") + patch_flags.assert_called_once() + + @parameterized.expand( + [ + ("not_an_object", "not-a-filter"), + ("dependency_without_key", {**_depends_on("dep"), "key": None}), + ] + ) + @mock.patch("posthog.client.flags") + def test_dependency_that_needs_the_server_falls_back_despite_malformed_later_condition( + self, _name, malformed_filter, patch_flags + ): + dep, target = self._target_depending_on( + {"key": "id", "type": "cohort", "value": 999} + ) + target["filters"]["groups"][1]["properties"] = [malformed_filter] + self.client.feature_flags = [dep, target] + patch_flags.return_value = { + "flags": {"target": {"enabled": True, "variant": "a"}} + } + + with mock.patch.object(self.client.log, "exception") as patch_exception: + flags = self.client.evaluate_flags("user-123", flag_keys=["target"]) + + self.assertEqual(flags.get_flag("target"), "a") + patch_flags.assert_called_once() + patch_exception.assert_not_called() + + @mock.patch("posthog.client.flags") + def test_dependency_that_needs_the_server_leaves_flag_unresolved_locally( + self, patch_flags + ): + self.client.feature_flags = self._target_depending_on( + {"key": "id", "type": "cohort", "value": 999} + ) + + flags = self.client.evaluate_flags("user-123", only_evaluate_locally=True) + + self.assertNotIn("target", flags.keys) + self.assertEqual( + dict(flags.unresolved_flags), + { + "dep": UnresolvedFlagReason.UNSUPPORTED_DEFINITION, + "target": UnresolvedFlagReason.UNRESOLVED_DEPENDENCY, + }, + ) + patch_flags.assert_not_called() + + @mock.patch("posthog.client.flags") + def test_dependency_resolved_locally_picks_its_condition(self, patch_flags): + self.client.feature_flags = self._target_depending_on(_PLAN_IS_PRO) + + flags = self.client.evaluate_flags( + "user-123", person_properties={"plan": "pro"} + ) + + self.assertEqual(flags.get_flag("target"), "a") + patch_flags.assert_not_called() + + @mock.patch("posthog.client.flags") + def test_flag_resolved_by_remote_fallback_is_not_unresolved(self, patch_flags): + patch_flags.return_value = { + "flags": {"checkout": {"enabled": True, "variant": None}} + } + + flags = self.client.evaluate_flags("user-123") + + self.assertIs(flags.get_flag("checkout"), True) + self.assertEqual(sorted(flags.keys), ["beta-ui", "checkout"]) + self.assertEqual(dict(flags.unresolved_flags), {}) + patch_flags.assert_called_once() + + @parameterized.expand( + [ + ( + "request_fails", + Exception("flags request failed"), + {}, + "local_evaluation_inconclusive", + ), + ("returns_no_flags", None, {}, "local_evaluation_inconclusive"), + ( + "errors_while_computing", + None, + {"errorsWhileComputingFlags": True}, + "errors_while_computing_flags,local_evaluation_inconclusive", + ), + ] + ) + @mock.patch("posthog.client.flags") + @mock.patch.object(Client, "capture") + def test_flag_left_without_remote_value_is_unresolved( + self, + _name, + side_effect, + extra_response, + expected_error, + patch_capture, + patch_flags, + ): + patch_flags.side_effect = side_effect + patch_flags.return_value = {"flags": {}, **extra_response} + + flags = self.client.evaluate_flags("user-123") + + self.assertNotIn("checkout", flags.keys) + self.assertEqual( + dict(flags.unresolved_flags), + {"checkout": UnresolvedFlagReason.EXPERIENCE_CONTINUITY}, + ) + flags.is_enabled("checkout") + properties = _flag_called_properties(patch_capture)["checkout"] + self.assertEqual(properties["$feature_flag_error"], expected_error) + + @mock.patch("posthog.client.flags") + @mock.patch.object(Client, "capture") + def test_key_without_local_definition_is_missing_not_unresolved( + self, patch_capture, patch_flags + ): + self.client.feature_flags = [_always_on_flag("beta-ui")] + patch_flags.return_value = {"flags": {}} + + flags = self.client.evaluate_flags( + "user-123", flag_keys=["beta-ui", "typo-flag"] + ) + flags.is_enabled("typo-flag") + + self.assertEqual(dict(flags.unresolved_flags), {}) + properties = _flag_called_properties(patch_capture)["typo-flag"] + self.assertEqual(properties["$feature_flag_error"], "flag_missing") + + @mock.patch("posthog.client.flags") + def test_flag_outside_requested_keys_is_not_unresolved(self, patch_flags): + flags = self.client.evaluate_flags("user-123", flag_keys=["beta-ui"]) + + self.assertEqual(flags.keys, ["beta-ui"]) + self.assertTrue(flags.is_enabled("beta-ui")) + self.assertEqual(dict(flags.unresolved_flags), {}) + patch_flags.assert_not_called() + + @mock.patch("posthog.client.flags") + def test_inactive_flag_is_false_not_unresolved(self, patch_flags): + self.client.feature_flags = [ + _always_on_flag("checkout", active=False, ensure_experience_continuity=True) + ] + + flags = self.client.evaluate_flags("user-123", only_evaluate_locally=True) + + self.assertEqual(flags.keys, ["checkout"]) + self.assertIs(flags.get_flag("checkout"), False) + self.assertEqual(dict(flags.unresolved_flags), {}) + patch_flags.assert_not_called() + + @mock.patch("posthog.client.flags") + @mock.patch.object(Client, "capture") + def test_reading_remote_resolved_flag_reports_no_error( + self, patch_capture, patch_flags + ): + patch_flags.return_value = { + "flags": {"checkout": {"enabled": True, "variant": None}} + } + + flags = self.client.evaluate_flags("user-123") + + self.assertTrue(flags.is_enabled("checkout")) + properties = _flag_called_properties(patch_capture)["checkout"] + self.assertIs(properties["$feature_flag_response"], True) + self.assertNotIn("$feature_flag_error", properties) + + @mock.patch("posthog.client.flags") + @mock.patch.object(Client, "capture") + def test_reading_unresolved_flag_reports_local_evaluation_inconclusive( + self, patch_capture, _patch_flags + ): + flags = self.client.evaluate_flags("user-123", only_evaluate_locally=True) + + self.assertFalse(flags.is_enabled("checkout")) + properties = _flag_called_properties(patch_capture)["checkout"] + self.assertEqual( + properties["$feature_flag_error"], "local_evaluation_inconclusive" + ) + + @mock.patch("posthog.client.flags") + @mock.patch.object(Client, "capture") + def test_filtered_snapshots_carry_no_unresolved_flags( + self, patch_capture, _patch_flags + ): + flags = self.client.evaluate_flags("user-123", only_evaluate_locally=True) + flags.is_enabled("beta-ui") + + for filtered in (flags.only(["beta-ui"]), flags.only_accessed()): + self.assertEqual(dict(filtered.unresolved_flags), {}) + filtered.get_flag("checkout") + properties = _flag_called_properties(patch_capture)["checkout"] + self.assertEqual(properties["$feature_flag_error"], "flag_missing") + self.assertEqual( + dict(flags.unresolved_flags), + {"checkout": UnresolvedFlagReason.EXPERIENCE_CONTINUITY}, + ) + + @mock.patch("posthog.client.flags") + @mock.patch.object(Client, "capture") + def test_unexpected_error_is_missing_not_unresolved( + self, patch_capture, _patch_flags + ): + broken = _always_on_flag("checkout") + broken["filters"]["groups"][0]["rollout_percentage"] = "abc" + self.client.feature_flags = [broken] + + flags = self.client.evaluate_flags("user-123", only_evaluate_locally=True) + flags.is_enabled("checkout") + + self.assertEqual(dict(flags.unresolved_flags), {}) + properties = _flag_called_properties(patch_capture)["checkout"] + self.assertEqual(properties["$feature_flag_error"], "flag_missing") + + @mock.patch("posthog.client.flags") + def test_filter_that_does_not_match_resolves_flag_locally(self, patch_flags): + self.client.feature_flags = [ + _flag_with( + "checkout", + _STATIC_COHORT, + {"key": "plan", "operator": "exact", "value": "free"}, + ) + ] + + flags = self.client.evaluate_flags( + "user-123", person_properties={"plan": "pro"} + ) + + self.assertIs(flags.get_flag("checkout"), False) + self.assertEqual(dict(flags.unresolved_flags), {}) + patch_flags.assert_not_called() + + +class TestUnresolvableFlagWarning(unittest.TestCase): + """Mirrors the ``@unresolvable_flag_warning_capable`` scenarios of the evaluate-flags spec.""" + + def _client(self, **kwargs): + client = Client( + FAKE_TEST_API_KEY, + secret_key="test", + enable_local_evaluation=False, + send=False, + **kwargs, + ) + self.addCleanup(client.shutdown) + return client + + @staticmethod + def _definitions(*flags): + return {"flags": list(flags), "group_type_mapping": {}, "cohorts": {}} + + @staticmethod + def _refresh(client, response): + with mock.patch("posthog.client.get", return_value=response): + client._load_feature_flags() + + def _refresh_with(self, client, *flags): + self._refresh( + client, + GetResponse(data=self._definitions(*flags), etag=None, not_modified=False), + ) + + @staticmethod + def _warnings(logs, key, reason): + return [ + record + for record in logs.records + if record.levelno == logging.WARNING + and f"(reason: {reason})" in record.getMessage() + and key in record.getMessage() + ] + + @parameterized.expand( + [("unchanged_definitions",), ("not_modified",), ("shared_cache_load",)] + ) + @mock.patch("posthog.client.flags") + def test_loading_never_resolvable_definition_logs_one_warning( + self, refresh_result, patch_flags + ): + definitions = self._definitions( + _always_on_flag("checkout", ensure_experience_continuity=True), + _always_on_flag("beta-ui"), + ) + cache_provider = mock.Mock() + cache_provider.should_fetch_flag_definitions.return_value = False + cache_provider.get_flag_definitions.return_value = definitions + client = self._client( + flag_definition_cache_provider=( + cache_provider if refresh_result == "shared_cache_load" else None + ) + ) + self.assertFalse(client.debug) + + with self.assertLogs("posthog", level=logging.DEBUG) as logs: + if refresh_result == "shared_cache_load": + client._load_feature_flags() + client._load_feature_flags() + else: + self._refresh( + client, + GetResponse(data=definitions, etag="etag-1", not_modified=False), + ) + self._refresh( + client, + GetResponse(data=None, etag="etag-1", not_modified=True) + if refresh_result == "not_modified" + else GetResponse( + data=definitions, etag="etag-2", not_modified=False + ), + ) + + warnings_logged = self._warnings(logs, "checkout", "experience_continuity") + self.assertEqual(len(warnings_logged), 1) + self.assertNotIn("beta-ui", warnings_logged[0].getMessage()) + self.assertTrue(client.log.isEnabledFor(logging.WARNING)) + patch_flags.assert_not_called() + + @mock.patch("posthog.client.flags") + def test_changed_definition_logs_warning_again(self, patch_flags): + client = self._client() + checkout = _always_on_flag("checkout", ensure_experience_continuity=True) + + with self.assertLogs("posthog", level=logging.WARNING) as logs: + self._refresh_with(client, checkout) + self._refresh_with(client, {**checkout, "id": 2}) + + self.assertEqual( + len(self._warnings(logs, "checkout", "experience_continuity")), 2 + ) + patch_flags.assert_not_called() + + def test_change_to_another_flag_does_not_repeat_warning(self): + client = self._client() + checkout = _always_on_flag("checkout", ensure_experience_continuity=True) + + with self.assertLogs("posthog", level=logging.WARNING) as logs: + self._refresh_with(client, checkout, _always_on_flag("beta-ui")) + self._refresh_with(client, checkout, _always_on_flag("beta-ui", id=2)) + + self.assertEqual( + len(self._warnings(logs, "checkout", "experience_continuity")), 1 + ) + + @parameterized.expand( + [ + ("static_cohort", _flag_with("checkout", _STATIC_COHORT)), + ( + "unknown_operator", + _flag_with( + "checkout", {"key": "plan", "operator": "bogus", "value": "x"} + ), + ), + ( + "malformed_value", + _flag_with( + "checkout", + {"key": "since", "operator": "is_date_after", "value": "x"}, + ), + ), + ("missing_dependency", _flag_with("checkout", _depends_on("missing"))), + ( + "circular_dependency", + _flag_with( + "checkout", {**_depends_on("checkout"), "dependency_chain": []} + ), + ), + ] + ) + def test_unsupported_definition_logs_warning(self, _name, checkout): + client = self._client() + + with self.assertLogs("posthog", level=logging.WARNING) as logs: + self._refresh_with(client, checkout) + self._refresh_with(client, checkout) + + self.assertEqual( + len(self._warnings(logs, "checkout", "unsupported_definition")), 1 + ) + + def test_does_not_warn_for_flags_that_depend_on_the_call(self): + client = self._client() + + with self.assertLogs("posthog", level=logging.WARNING) as logs: + self._refresh_with( + client, + _always_on_flag("continuity", ensure_experience_continuity=True), + _always_on_flag( + "inactive", active=False, ensure_experience_continuity=True + ), + _flag_with("needs-plan", _PLAN_IS_PRO), + _flag_with("dependent", _depends_on("continuity")), + ) + + messages = " ".join(record.getMessage() for record in logs.records) + self.assertIn("continuity", messages) + for key in ("inactive", "needs-plan", "dependent"): + self.assertNotIn(key, messages) + + def test_malformed_flag_does_not_block_other_warnings(self): + client = self._client() + broken = {"key": "broken", "active": True, "filters": {"groups": [None]}} + + with self.assertLogs("posthog", level=logging.WARNING) as logs: + self._refresh_with( + client, + broken, + _always_on_flag("checkout", ensure_experience_continuity=True), + ) + + self.assertEqual( + len(self._warnings(logs, "checkout", "experience_continuity")), 1 + ) + + def test_removed_flag_is_forgotten_and_warns_again_when_it_returns(self): + client = self._client() + checkout = _always_on_flag("checkout", ensure_experience_continuity=True) + + with self.assertLogs("posthog", level=logging.WARNING) as logs: + self._refresh_with(client, checkout) + self._refresh_with(client, _always_on_flag("beta-ui")) + self.assertEqual(client._warned_unresolvable_flags, {}) + self._refresh_with(client, checkout) + + self.assertEqual( + len(self._warnings(logs, "checkout", "experience_continuity")), 2 + ) + + def test_classifies_and_logs_outside_the_publication_lock(self): + client = self._client() + lock = client._flag_definition_publication_lock + lock_held = [] + classify = Client._classify_unresolvable_flags + + def record_lock_and_classify(snapshot): + lock_held.append(lock._is_owned()) + return classify(snapshot) + + with ( + mock.patch.object( + Client, + "_classify_unresolvable_flags", + side_effect=record_lock_and_classify, + ), + mock.patch.object( + client.log, + "warning", + side_effect=lambda *args, **kwargs: lock_held.append(lock._is_owned()), + ), + ): + self._refresh_with( + client, _always_on_flag("checkout", ensure_experience_continuity=True) + ) + + self.assertEqual(lock_held, [False, False]) + + class TestEvaluateFlagsFiltering(unittest.TestCase): def setUp(self): self.client = Client(FAKE_TEST_API_KEY) diff --git a/posthog/test/test_feature_flags.py b/posthog/test/test_feature_flags.py index 2c430336..14a1ed18 100644 --- a/posthog/test/test_feature_flags.py +++ b/posthog/test/test_feature_flags.py @@ -19,14 +19,17 @@ _UNHANDLED_OPERATOR_MESSAGE, InconclusiveMatchError, RequiresServerEvaluation, + _get_definition_unresolved_reason, + _pick_unresolved_reason, match_cohort, + match_feature_flag_properties, match_property, match_property_group, parse_datetime, relative_date_parse_for_feature_flag_matching, ) from posthog.request import APIError, GetResponse -from posthog.types import FeatureFlagEvaluationRuntime +from posthog.types import FeatureFlagEvaluationRuntime, UnresolvedFlagReason from posthog.test.test_utils import FAKE_TEST_API_KEY from posthog.utils import FlagCache @@ -2006,14 +2009,15 @@ def test_feature_flags_with_flag_dependencies(self, patch_flags, mock_log): self.assertEqual(patch_flags.call_count, 1) patch_load.assert_not_called() - # Test with email that doesn't match (should also fall back to remote due to missing dependency) + # An email that doesn't match rules the condition out whatever the missing + # dependency would evaluate to, so the flag resolves locally. feature_flag_match = client.get_feature_flag( "flag-with-dependencies", "test-user-2", person_properties={"email": "test@other.com"}, ) - self.assertIsNone(feature_flag_match) - self.assertEqual(patch_flags.call_count, 2) # Called twice now + self.assertIs(feature_flag_match, False) + self.assertEqual(patch_flags.call_count, 1) patch_load.assert_not_called() @mock.patch("posthog.client.flags") @@ -5367,6 +5371,153 @@ def property(self, key, value, operator=None): return result + @parameterized.expand( + [ + ("unknown_operator", "x", "bogus", {"key": "x"}, "UNSUPPORTED_DEFINITION"), + ("missing_property", "x", "exact", {}, "MISSING_CONTEXT"), + ( + "invalid_flag_date", + "not-a-date", + "is_date_before", + {"key": "2024-01-01"}, + "UNSUPPORTED_DEFINITION", + ), + ( + "invalid_property_date", + "2024-01-01", + "is_date_before", + {"key": "not-a-date"}, + "MISSING_CONTEXT", + ), + ( + "invalid_flag_semver", + "x.y", + "semver_gt", + {"key": "1.2.3"}, + "UNSUPPORTED_DEFINITION", + ), + ( + "invalid_property_semver", + "1.2.3", + "semver_gt", + {"key": "x.y"}, + "MISSING_CONTEXT", + ), + ( + "invalid_flag_and_property_semver", + "x.y", + "semver_gt", + {"key": "a.b"}, + "UNSUPPORTED_DEFINITION", + ), + ( + "invalid_flag_semver_and_missing_property", + "x.y", + "semver_gt", + {}, + "UNSUPPORTED_DEFINITION", + ), + ( + "invalid_flag_wildcard_and_property", + "1x.*", + "semver_wildcard", + {"key": "a.b"}, + "UNSUPPORTED_DEFINITION", + ), + ( + "boolean_flag_date", + True, + "is_date_after", + {"key": "2024-01-01"}, + "UNSUPPORTED_DEFINITION", + ), + ( + "non_date_property", + "2024-01-01", + "is_date_after", + {"key": 5}, + "MISSING_CONTEXT", + ), + ( + "invalid_flag_number", + "abc", + "gt", + {"key": 5}, + "UNSUPPORTED_DEFINITION", + ), + ( + "invalid_flag_number_and_missing_property", + "abc", + "gt", + {}, + "UNSUPPORTED_DEFINITION", + ), + ( + "non_json_property", + "x", + "exact", + {"key": object()}, + "MISSING_CONTEXT", + ), + ( + "non_finite_property", + "x", + "icontains", + {"key": float("nan")}, + "MISSING_CONTEXT", + ), + ( + "non_finite_flag_value", + float("nan"), + "exact", + {"key": "x"}, + "UNSUPPORTED_DEFINITION", + ), + ( + "non_finite_flag_value_and_property", + [float("inf")], + "exact", + {"key": float("nan")}, + "UNSUPPORTED_DEFINITION", + ), + ( + "non_finite_flag_value_and_missing_property", + float("nan"), + "icontains", + {}, + "UNSUPPORTED_DEFINITION", + ), + ( + "deferred_regex", + r"\bfoo", + "regex", + {"key": "foo"}, + "UNSUPPORTED_DEFINITION", + ), + ( + "deferred_regex_and_missing_property", + r"\bfoo", + "regex", + {}, + "UNSUPPORTED_DEFINITION", + ), + ( + "regex_deferred_for_property_value", + r"\w+", + "regex", + {"key": "é"}, + "UNSUPPORTED_DEFINITION", + ), + ] + ) + def test_inconclusive_match_reports_reason( + self, _name, value, operator, properties, reason + ): + with self.assertRaises(InconclusiveMatchError) as ctx: + match_property(self.property("key", value, operator), properties) + + self.assertEqual(ctx.exception.reason, UnresolvedFlagReason[reason]) + def test_every_supported_operator_has_a_dispatch_branch(self): # PROPERTY_OPERATORS gates local evaluation, but match_property dispatches # on a hand-written if-chain. An operator listed in the tuple but missing a @@ -6403,6 +6554,399 @@ def test_unknown_operator(self): ) +PLAN_IS_PRO = {"key": "plan", "operator": "exact", "value": "pro", "type": "person"} +PLAN_IS_FREE = {"key": "plan", "operator": "exact", "value": "free", "type": "person"} +EMAIL_IS_SET = {"key": "email", "operator": "is_set", "type": "person"} +UNKNOWN_OPERATOR = {"key": "plan", "operator": "bogus", "value": "x", "type": "person"} +STATIC_COHORT = {"key": "id", "value": 42, "type": "cohort"} + + +def _depends_on(key): + return { + "key": key, + "type": "flag", + "value": True, + "operator": "flag_evaluates_to", + "dependency_chain": [key], + } + + +def _flag(key, *conditions, **extra): + return { + "key": key, + "active": True, + "filters": { + "groups": [ + {"properties": list(properties), "rollout_percentage": 100} + for properties in conditions + ] + }, + **extra, + } + + +class TestUnresolvedReasons(unittest.TestCase): + def evaluate(self, flag, properties, flags=(), cohorts=None): + return match_feature_flag_properties( + flag, + "user-123", + properties, + cohort_properties=cohorts or {}, + flags_by_key={f["key"]: f for f in (flag, *flags)}, + evaluation_cache={}, + bucketing_value="user-123", + ) + + def assert_unresolved( + self, error_type, reason, flag, properties, flags=(), cohorts=None + ): + with self.assertRaises(error_type) as ctx: + self.evaluate(flag, properties, flags, cohorts) + self.assertIs(type(ctx.exception), error_type) + self.assertEqual(ctx.exception.reason, reason) + + @parameterized.expand( + [ + ( + "experience_continuity_first", + [None, UnresolvedFlagReason.EXPERIENCE_CONTINUITY], + UnresolvedFlagReason.EXPERIENCE_CONTINUITY, + ), + ( + "unsupported_over_missing", + [ + UnresolvedFlagReason.MISSING_CONTEXT, + UnresolvedFlagReason.UNSUPPORTED_DEFINITION, + ], + UnresolvedFlagReason.UNSUPPORTED_DEFINITION, + ), + ( + "dependency_over_missing", + [ + UnresolvedFlagReason.MISSING_CONTEXT, + UnresolvedFlagReason.UNRESOLVED_DEPENDENCY, + ], + UnresolvedFlagReason.UNRESOLVED_DEPENDENCY, + ), + ( + "unsupported_kept_over_dependency", + [ + UnresolvedFlagReason.UNSUPPORTED_DEFINITION, + UnresolvedFlagReason.UNRESOLVED_DEPENDENCY, + ], + UnresolvedFlagReason.UNSUPPORTED_DEFINITION, + ), + ( + "experience_continuity_kept", + [ + UnresolvedFlagReason.EXPERIENCE_CONTINUITY, + UnresolvedFlagReason.UNSUPPORTED_DEFINITION, + ], + UnresolvedFlagReason.EXPERIENCE_CONTINUITY, + ), + ] + ) + def test_pick_unresolved_reason_follows_spec_order(self, _name, reasons, expected): + current, new = reasons + self.assertEqual(_pick_unresolved_reason(current, new), expected) + + @parameterized.expand( + [ + ("missing_property_first", [EMAIL_IS_SET, PLAN_IS_FREE]), + ("missing_property_last", [PLAN_IS_FREE, EMAIL_IS_SET]), + ("static_cohort_first", [STATIC_COHORT, PLAN_IS_FREE]), + ("unknown_operator_first", [UNKNOWN_OPERATOR, PLAN_IS_FREE]), + ] + ) + def test_filter_that_does_not_match_rules_condition_out(self, _name, properties): + self.assertIs( + self.evaluate(_flag("checkout", properties), {"plan": "pro"}), False + ) + + with self.assertRaises((InconclusiveMatchError, RequiresServerEvaluation)): + self.evaluate(_flag("checkout", properties), {"plan": "free"}) + + def test_condition_reports_highest_precedence_reason(self): + self.assert_unresolved( + InconclusiveMatchError, + UnresolvedFlagReason.UNSUPPORTED_DEFINITION, + _flag("checkout", [PLAN_IS_PRO, UNKNOWN_OPERATOR]), + {}, + ) + + def test_static_cohort_first_unchecked_filter_requires_server(self): + self.assert_unresolved( + RequiresServerEvaluation, + UnresolvedFlagReason.UNSUPPORTED_DEFINITION, + _flag("checkout", [STATIC_COHORT, PLAN_IS_PRO]), + {}, + ) + + def test_static_cohort_after_unchecked_filter_lets_later_conditions_resolve(self): + flag = _flag("checkout", [PLAN_IS_PRO, STATIC_COHORT], [EMAIL_IS_SET]) + + self.assertIs(self.evaluate(flag, {"email": "a@b.c"}), True) + self.assert_unresolved( + InconclusiveMatchError, + UnresolvedFlagReason.UNSUPPORTED_DEFINITION, + flag, + {}, + ) + + def test_later_static_cohort_condition_keeps_higher_precedence_reason(self): + self.assert_unresolved( + RequiresServerEvaluation, + UnresolvedFlagReason.UNSUPPORTED_DEFINITION, + _flag("checkout", [PLAN_IS_PRO], [STATIC_COHORT]), + {}, + ) + + @parameterized.expand( + [ + ("dependency_only", [[_depends_on("base")]], "unresolved_dependency"), + ( + "unknown_operator_first", + [[UNKNOWN_OPERATOR], [_depends_on("base")]], + "unsupported_definition", + ), + ( + "dependency_first", + [[_depends_on("base")], [UNKNOWN_OPERATOR]], + "unsupported_definition", + ), + ( + "dependency_reused_after_unchecked_filter", + [[EMAIL_IS_SET, _depends_on("base")], [_depends_on("base")]], + "unresolved_dependency", + ), + ( + "unknown_operator_after_dependency_in_same_condition", + [[_depends_on("base"), UNKNOWN_OPERATOR]], + "unsupported_definition", + ), + ( + "cohort_with_dependency_first", + [[{"key": "id", "value": 1, "type": "cohort"}]], + "unsupported_definition", + ), + ( + "cohort_with_unknown_operator_first", + [[{"key": "id", "value": 2, "type": "cohort"}]], + "unsupported_definition", + ), + ] + ) + def test_dependency_requiring_server_requires_server( + self, _name, conditions, reason + ): + cohorts = { + "1": {"type": "AND", "values": [_depends_on("base"), UNKNOWN_OPERATOR]}, + "2": {"type": "AND", "values": [UNKNOWN_OPERATOR, _depends_on("base")]}, + } + + self.assert_unresolved( + RequiresServerEvaluation, + UnresolvedFlagReason(reason), + _flag("checkout", *conditions), + {"plan": "pro"}, + flags=[_flag("base", [STATIC_COHORT])], + cohorts=cohorts, + ) + + def test_unexpected_error_after_inconclusive_filter_lets_later_conditions_resolve( + self, + ): + malformed_filter = "not-a-filter" + + self.assertIs( + self.evaluate( + _flag("checkout", [PLAN_IS_PRO, malformed_filter], [EMAIL_IS_SET]), + {"email": "a@b.c"}, + ), + True, + ) + with self.assertRaises(AttributeError): + self.evaluate( + _flag("checkout", [malformed_filter, PLAN_IS_PRO], [EMAIL_IS_SET]), + {"email": "a@b.c"}, + ) + + def test_dependency_with_experience_continuity_is_unresolved_dependency(self): + base = _flag("base", [], ensure_experience_continuity=True) + + self.assert_unresolved( + InconclusiveMatchError, + UnresolvedFlagReason.UNRESOLVED_DEPENDENCY, + _flag("checkout", [_depends_on("base")]), + {}, + flags=[base], + ) + + def test_inactive_dependency_with_experience_continuity_is_false(self): + base = _flag("base", [], active=False, ensure_experience_continuity=True) + checkout = _flag("checkout", [{**_depends_on("base"), "value": False}]) + + self.assertIs(self.evaluate(checkout, {}, flags=[base]), True) + + def test_missing_dependency_outranks_inconclusive_one_earlier_in_chain(self): + base = _flag("base", [PLAN_IS_PRO]) + dependency = {**_depends_on("missing"), "dependency_chain": ["base", "missing"]} + + self.assert_unresolved( + InconclusiveMatchError, + UnresolvedFlagReason.UNSUPPORTED_DEFINITION, + _flag("checkout", [dependency]), + {}, + flags=[base], + ) + + @parameterized.expand( + [ + ("circular", []), + ("not_a_list", None), + ] + ) + def test_malformed_dependency_chain_is_unsupported_definition( + self, _name, dependency_chain + ): + dependency = {**_depends_on("base"), "dependency_chain": dependency_chain} + + self.assert_unresolved( + InconclusiveMatchError, + UnresolvedFlagReason.UNSUPPORTED_DEFINITION, + _flag("checkout", [dependency]), + {}, + flags=[_flag("base", [])], + ) + + def test_missing_group_properties_is_missing_context(self): + flag = _flag("checkout", []) + flag["filters"]["groups"][0]["aggregation_group_type_index"] = 0 + + with self.assertRaises(InconclusiveMatchError) as ctx: + match_feature_flag_properties( + flag, + "user-123", + {}, + flags_by_key={"checkout": flag}, + evaluation_cache={}, + bucketing_value="user-123", + group_type_mapping={"0": "company"}, + groups={"company": "acme"}, + ) + self.assertEqual(ctx.exception.reason, UnresolvedFlagReason.MISSING_CONTEXT) + + def test_unsupported_cohort_operator_outranks_member_missing_context(self): + with self.assertRaises(InconclusiveMatchError) as ctx: + match_cohort( + {"key": "id", "value": 1, "type": "cohort", "operator": "bogus"}, + {}, + {"1": {"type": "AND", "values": [PLAN_IS_PRO]}}, + ) + self.assertEqual( + ctx.exception.reason, UnresolvedFlagReason.UNSUPPORTED_DEFINITION + ) + + def test_cohort_group_keeps_highest_precedence_reason(self): + with self.assertRaises(InconclusiveMatchError) as ctx: + match_property_group( + {"type": "AND", "values": [PLAN_IS_PRO, UNKNOWN_OPERATOR]}, {}, {} + ) + self.assertEqual( + ctx.exception.reason, UnresolvedFlagReason.UNSUPPORTED_DEFINITION + ) + + @parameterized.expand( + [ + ("resolvable", _flag("checkout", [PLAN_IS_PRO]), None), + ("inactive", _flag("checkout", [STATIC_COHORT], active=False), None), + ( + "experience_continuity", + _flag("checkout", [STATIC_COHORT], ensure_experience_continuity=True), + UnresolvedFlagReason.EXPERIENCE_CONTINUITY, + ), + ( + "static_cohort", + _flag("checkout", [PLAN_IS_PRO], [STATIC_COHORT]), + UnresolvedFlagReason.UNSUPPORTED_DEFINITION, + ), + ( + "unknown_operator", + _flag("checkout", [UNKNOWN_OPERATOR]), + UnresolvedFlagReason.UNSUPPORTED_DEFINITION, + ), + ( + "malformed_value", + _flag( + "checkout", + [{"key": "n", "operator": "gt", "value": "abc", "type": "person"}], + ), + UnresolvedFlagReason.UNSUPPORTED_DEFINITION, + ), + ( + "missing_dependency", + _flag("checkout", [_depends_on("missing")]), + UnresolvedFlagReason.UNSUPPORTED_DEFINITION, + ), + ( + "circular_dependency", + _flag( + "checkout", [{**_depends_on("checkout"), "dependency_chain": []}] + ), + UnresolvedFlagReason.UNSUPPORTED_DEFINITION, + ), + ( + "malformed_dependency_shape", + _flag("checkout", [{**_depends_on("missing"), "operator": "exact"}]), + None, + ), + ( + "unknown_group_type", + { + **_flag("checkout", []), + "filters": {"aggregation_group_type_index": 9, "groups": []}, + }, + UnresolvedFlagReason.UNSUPPORTED_DEFINITION, + ), + ( + "static_cohort_nested_in_cohort", + _flag("checkout", [{"key": "id", "value": 1, "type": "cohort"}]), + UnresolvedFlagReason.UNSUPPORTED_DEFINITION, + ), + ( + "cohort_cycle", + _flag("checkout", [{"key": "id", "value": 2, "type": "cohort"}]), + None, + ), + ( + "unknown_cohort_operator", + _flag( + "checkout", + [{"key": "id", "value": 2, "type": "cohort", "operator": "bogus"}], + ), + UnresolvedFlagReason.UNSUPPORTED_DEFINITION, + ), + ] + ) + def test_definition_unresolved_reason(self, _name, flag, expected): + cohorts = { + "1": {"type": "OR", "values": [STATIC_COHORT]}, + "2": { + "type": "OR", + "values": [{"key": "id", "value": 3, "type": "cohort"}], + }, + "3": { + "type": "OR", + "values": [{"key": "id", "value": 2, "type": "cohort"}], + }, + } + + self.assertEqual( + _get_definition_unresolved_reason(flag, {flag["key"]: flag}, cohorts, {}), + expected, + ) + + class TestDateParsing(unittest.TestCase): @parameterized.expand( [ diff --git a/posthog/types.py b/posthog/types.py index 30833931..994b0786 100644 --- a/posthog/types.py +++ b/posthog/types.py @@ -99,6 +99,32 @@ def matches(self, other: "FeatureFlagEvaluationRuntime") -> bool: return self is other or FeatureFlagEvaluationRuntime.ALL in (self, other) +class UnresolvedFlagReason(str, Enum): + """Why local evaluation left a flag without a value. + + Reported by :attr:`FeatureFlagEvaluations.unresolved_flags`. Inheriting from + ``str`` keeps the members directly comparable to their string values. When more + than one cause applies, the first reason in the order below is reported. + + Values: + EXPERIENCE_CONTINUITY: The flag has experience continuity enabled, which + local evaluation never resolves. Change the flag to fix it. + UNSUPPORTED_DEFINITION: The flag uses something the local evaluator does not + support, such as a static cohort, an unknown operator, a malformed value, + a dependency with no loaded definition or a circular dependency. Change + the flag or upgrade the SDK to fix it. + UNRESOLVED_DEPENDENCY: A flag this flag depends on has a loaded definition + but was unresolved. + MISSING_CONTEXT: The call did not pass a property, group property or device + id the flag needs, or passed it in an unusable form. Pass it to fix it. + """ + + EXPERIENCE_CONTINUITY = "experience_continuity" + UNSUPPORTED_DEFINITION = "unsupported_definition" + UNRESOLVED_DEPENDENCY = "unresolved_dependency" + MISSING_CONTEXT = "missing_context" + + @dataclass(frozen=True) class FlagReason: """Reason metadata returned by the feature flag API. @@ -422,6 +448,8 @@ class FeatureFlagError: Error values: ERRORS_WHILE_COMPUTING: Server returned errorsWhileComputingFlags=true FLAG_MISSING: Requested flag not in API response + LOCAL_EVALUATION_INCONCLUSIVE: Requested flag has a loaded definition but + local evaluation was inconclusive and no remote value filled the gap QUOTA_LIMITED: Rate/quota limit exceeded TIMEOUT: Request timed out CONNECTION_ERROR: Network connectivity issue @@ -433,6 +461,7 @@ class FeatureFlagError: ERRORS_WHILE_COMPUTING = "errors_while_computing_flags" FLAG_MISSING = "flag_missing" + LOCAL_EVALUATION_INCONCLUSIVE = "local_evaluation_inconclusive" QUOTA_LIMITED = "quota_limited" TIMEOUT = "timeout" CONNECTION_ERROR = "connection_error" diff --git a/references/public_api_snapshot.txt b/references/public_api_snapshot.txt index 960d020d..9a5142ad 100644 --- a/references/public_api_snapshot.txt +++ b/references/public_api_snapshot.txt @@ -30,6 +30,7 @@ alias posthog.OptionalSetArgs -> posthog.args.OptionalSetArgs alias posthog.RequiresServerEvaluation -> posthog.feature_flags.RequiresServerEvaluation alias posthog.SocketOptions -> posthog.request.SocketOptions alias posthog.Span -> posthog.tracing.span.Span +alias posthog.UnresolvedFlagReason -> posthog.types.UnresolvedFlagReason alias posthog.VERSION -> posthog.version.VERSION alias posthog.ai.PromptResult -> posthog.ai.prompts.PromptResult alias posthog.ai.PromptSource -> posthog.ai.prompts.PromptSource @@ -263,6 +264,7 @@ alias posthog.client.RequiresServerEvaluation -> posthog.feature_flags.RequiresS alias posthog.client.SendFeatureFlagsOptions -> posthog.types.SendFeatureFlagsOptions alias posthog.client.SizeLimitedDict -> posthog.utils.SizeLimitedDict alias posthog.client.Span -> posthog.tracing.span.Span +alias posthog.client.UnresolvedFlagReason -> posthog.types.UnresolvedFlagReason alias posthog.client.VERSION -> posthog.version.VERSION alias posthog.client.clean -> posthog.utils.clean alias posthog.client.determine_server_host -> posthog.request.determine_server_host @@ -304,7 +306,9 @@ alias posthog.exception_capture.Client -> posthog.client.Client alias posthog.exception_utils.ExcInfo -> posthog.args.ExcInfo alias posthog.exception_utils.ExceptionArg -> posthog.args.ExceptionArg alias posthog.feature_flag_evaluations.FlagValue -> posthog.types.FlagValue +alias posthog.feature_flag_evaluations.UnresolvedFlagReason -> posthog.types.UnresolvedFlagReason alias posthog.feature_flags.FlagValue -> posthog.types.FlagValue +alias posthog.feature_flags.UnresolvedFlagReason -> posthog.types.UnresolvedFlagReason alias posthog.feature_flags.convert_to_datetime_aware -> posthog.utils.convert_to_datetime_aware alias posthog.feature_flags.is_valid_regex -> posthog.utils.is_valid_regex alias posthog.feature_flags.utils -> posthog.utils @@ -898,14 +902,17 @@ attribute posthog.exception_utils.VariableSizeLimiter.current_size = 0 attribute posthog.exception_utils.VariableSizeLimiter.max_size = max_size attribute posthog.exception_utils.epoch = datetime(1970, 1, 1) attribute posthog.feature_flag_evaluations.FeatureFlagEvaluations.keys: List[str] +attribute posthog.feature_flag_evaluations.FeatureFlagEvaluations.unresolved_flags: Mapping[str, UnresolvedFlagReason] attribute posthog.feature_flags.ConditionMatch.MATCH = 'match' attribute posthog.feature_flags.ConditionMatch.NO_MATCH = 'no_match' attribute posthog.feature_flags.ConditionMatch.OUT_OF_ROLLOUT_BOUND = 'out_of_rollout_bound' attribute posthog.feature_flags.DATE_OPERATORS = ('is_date_before', 'is_date_after') attribute posthog.feature_flags.EQUALITY_OPERATORS = ('exact', 'is_not', 'is_set', 'is_not_set') +attribute posthog.feature_flags.InconclusiveMatchError.reason: UnresolvedFlagReason = UnresolvedFlagReason.UNSUPPORTED_DEFINITION attribute posthog.feature_flags.NONE_VALUES_ALLOWED_OPERATORS = ['is_not'] attribute posthog.feature_flags.NUMERIC_OPERATORS = ('gt', 'gte', 'lt', 'lte') attribute posthog.feature_flags.PROPERTY_OPERATORS = EQUALITY_OPERATORS + STRING_OPERATORS + NUMERIC_OPERATORS + DATE_OPERATORS + SEMVER_OPERATORS +attribute posthog.feature_flags.RequiresServerEvaluation.reason: UnresolvedFlagReason = UnresolvedFlagReason.UNSUPPORTED_DEFINITION attribute posthog.feature_flags.SEMVER_COMPARISON_OPERATORS = ('semver_eq', 'semver_neq', 'semver_gt', 'semver_gte', 'semver_lt', 'semver_lte') attribute posthog.feature_flags.SEMVER_OPERATORS = SEMVER_COMPARISON_OPERATORS + SEMVER_RANGE_OPERATORS attribute posthog.feature_flags.SEMVER_RANGE_OPERATORS = ('semver_tilde', 'semver_caret', 'semver_wildcard') @@ -1120,6 +1127,7 @@ attribute posthog.types.FeatureFlag.variant: Optional[str] attribute posthog.types.FeatureFlagError.CONNECTION_ERROR = 'connection_error' attribute posthog.types.FeatureFlagError.ERRORS_WHILE_COMPUTING = 'errors_while_computing_flags' attribute posthog.types.FeatureFlagError.FLAG_MISSING = 'flag_missing' +attribute posthog.types.FeatureFlagError.LOCAL_EVALUATION_INCONCLUSIVE = 'local_evaluation_inconclusive' attribute posthog.types.FeatureFlagError.QUOTA_LIMITED = 'quota_limited' attribute posthog.types.FeatureFlagError.TIMEOUT = 'timeout' attribute posthog.types.FeatureFlagError.UNKNOWN_ERROR = 'unknown_error' @@ -1154,6 +1162,10 @@ attribute posthog.types.SendFeatureFlagsOptions.group_properties: Optional[dict[ attribute posthog.types.SendFeatureFlagsOptions.only_evaluate_locally: Optional[bool] attribute posthog.types.SendFeatureFlagsOptions.person_properties: Optional[dict[str, Any]] attribute posthog.types.SendFeatureFlagsOptions.should_send: bool +attribute posthog.types.UnresolvedFlagReason.EXPERIENCE_CONTINUITY = 'experience_continuity' +attribute posthog.types.UnresolvedFlagReason.MISSING_CONTEXT = 'missing_context' +attribute posthog.types.UnresolvedFlagReason.UNRESOLVED_DEPENDENCY = 'unresolved_dependency' +attribute posthog.types.UnresolvedFlagReason.UNSUPPORTED_DEFINITION = 'unsupported_definition' attribute posthog.utils.CACHE_KEY_PREFIX = 'posthog:flags:' attribute posthog.utils.CACHE_MAX_SIZE = 10000 attribute posthog.utils.CACHE_STALE_TTL = 3600 @@ -1281,10 +1293,10 @@ class posthog.contexts.ContextScope(parent=None, fresh: bool = False, capture_ex class posthog.exception_capture.ExceptionCapture(client: Client, rate_limiting_enabled=False, bucket_size=DEFAULT_BUCKET_SIZE, refill_rate=DEFAULT_REFILL_RATE, refill_interval_seconds=DEFAULT_REFILL_INTERVAL_SECONDS) class posthog.exception_utils.AnnotatedValue(value, metadata) class posthog.exception_utils.VariableSizeLimiter(max_size=DEFAULT_TOTAL_VARIABLES_SIZE_LIMIT) -class posthog.feature_flag_evaluations.FeatureFlagEvaluations(host: _FeatureFlagEvaluationsHost, distinct_id: str, flags: Dict[str, _EvaluatedFlagRecord], groups: Optional[Mapping[str, Union[str, int]]] = None, disable_geoip: Optional[bool] = None, request_id: Optional[str] = None, evaluated_at: Optional[int] = None, errors_while_computing: bool = False, quota_limited: bool = False, minimal_flag_called_events: bool = False, accessed: Optional[Set[str]] = None) +class posthog.feature_flag_evaluations.FeatureFlagEvaluations(host: _FeatureFlagEvaluationsHost, distinct_id: str, flags: Dict[str, _EvaluatedFlagRecord], groups: Optional[Mapping[str, Union[str, int]]] = None, disable_geoip: Optional[bool] = None, request_id: Optional[str] = None, evaluated_at: Optional[int] = None, errors_while_computing: bool = False, quota_limited: bool = False, minimal_flag_called_events: bool = False, accessed: Optional[Set[str]] = None, unresolved_flags: Optional[Mapping[str, UnresolvedFlagReason]] = None) class posthog.feature_flags.ConditionMatch -class posthog.feature_flags.InconclusiveMatchError -class posthog.feature_flags.RequiresServerEvaluation +class posthog.feature_flags.InconclusiveMatchError(*args, reason: Optional[UnresolvedFlagReason] = None) +class posthog.feature_flags.RequiresServerEvaluation(*args, reason: Optional[UnresolvedFlagReason] = None) class posthog.flag_definition_cache.FlagDefinitionCacheData class posthog.flag_definition_cache.FlagDefinitionCacheProvider class posthog.integrations.asgi.PosthogASGIMiddleware(app: _ASGIApp, client: Optional[Client] = None, capture_exceptions: Optional[bool] = None, request_filter: Optional[_RequestFilter] = None, extra_properties: Optional[_ExtraProperties] = None, trust_tracing_headers: Optional[bool] = None, *, capture_tracing_headers: Optional[bool] = None) @@ -1325,6 +1337,7 @@ class posthog.types.FlagsAndPayloads class posthog.types.FlagsResponse class posthog.types.LegacyFlagMetadata(payload: Any) class posthog.types.SendFeatureFlagsOptions +class posthog.types.UnresolvedFlagReason class posthog.utils.FlagCache(max_size=CACHE_MAX_SIZE, default_ttl=CACHE_TTL) class posthog.utils.FlagCacheEntry(flag_result, flag_definition_version, timestamp=None) class posthog.utils.RedisFlagCache(redis_client, default_ttl=CACHE_TTL, stale_ttl=CACHE_STALE_TTL, key_prefix=CACHE_KEY_PREFIX) From a5fd58e3018795c842d294327171c1b6ee5ad4ca Mon Sep 17 00:00:00 2001 From: "releaser-posthog-python[bot]" <262414804+releaser-posthog-python[bot]@users.noreply.github.com> Date: Wed, 28 Oct 2026 21:22:04 +0000 Subject: [PATCH 36/60] chore: Release v7.79.0 [skip ci] (cherry picked from commit 91d3188bb4dbf5ed8e59165f0dbd62836a05d384) --- .sampo/changesets/unresolved-local-flags.md | 13 ------------- openfeature-provider/CHANGELOG.md | 6 ++++++ openfeature-provider/pyproject.toml | 2 +- posthog/CHANGELOG.md | 14 ++++++++++++++ posthog/version.py | 2 +- pyproject.toml | 2 +- uv.lock | 4 ++-- 7 files changed, 25 insertions(+), 18 deletions(-) delete mode 100644 .sampo/changesets/unresolved-local-flags.md diff --git a/.sampo/changesets/unresolved-local-flags.md b/.sampo/changesets/unresolved-local-flags.md deleted file mode 100644 index 135465dc..00000000 --- a/.sampo/changesets/unresolved-local-flags.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -pypi/posthog: minor ---- - -Report flags that local evaluation could not resolve on `evaluate_flags()` snapshots. `FeatureFlagEvaluations.unresolved_flags` maps each such flag to an `UnresolvedFlagReason` (`experience_continuity`, `unsupported_definition`, `unresolved_dependency` or `missing_context`), and reading one reports the `local_evaluation_inconclusive` error instead of `flag_missing`. When definitions load, log one warning per flag that local evaluation can never resolve (experience continuity, a static cohort, an unknown operator, a malformed value, or a missing or circular flag dependency), and again only when its definition changes. `AsyncClient` evaluates only remotely, so its snapshots have no unresolved flags. - -Behavior changes: - -- A condition with a filter that local evaluation can't check and a filter that does not match now resolves locally as no match. Before, such a flag fell back to a remote request, or stayed unresolved with `only_evaluate_locally=True`. -- An inactive flag with experience continuity enabled now resolves to `false` locally instead of falling back to a remote request. -- A flag that depends on a flag with experience continuity enabled is no longer evaluated locally. It falls back to a remote request, or stays unresolved with `only_evaluate_locally=True`. -- On `$feature_flag_called` events from an `evaluate_flags()` snapshot, `$feature_flag_error` is now `local_evaluation_inconclusive` instead of `flag_missing` for a flag that has a loaded definition but that local evaluation could not resolve. -- `get_all_flags()`, `get_all_flags_and_payloads()` and `evaluate_flags()` no longer log an error with a traceback when a flag needs the server (for example, a static cohort). They fall back to a remote request without logging, as `get_feature_flag()` already did. diff --git a/openfeature-provider/CHANGELOG.md b/openfeature-provider/CHANGELOG.md index 8ab8f1da..b06b9903 100644 --- a/openfeature-provider/CHANGELOG.md +++ b/openfeature-provider/CHANGELOG.md @@ -4,6 +4,12 @@ All notable changes to `openfeature-provider-posthog` are documented here. This file is maintained by [Sampo](https://github.com/bruits/sampo) from changesets in `.sampo/changesets/` that target `pypi/openfeature-provider-posthog`. +## 0.1.130 — 2026-10-28 + +### Patch changes + +- Updated dependencies: posthog@7.79.0 + ## 0.1.129 — 2026-10-28 ### Patch changes diff --git a/openfeature-provider/pyproject.toml b/openfeature-provider/pyproject.toml index be34d793..2d23f2d0 100644 --- a/openfeature-provider/pyproject.toml +++ b/openfeature-provider/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "openfeature-provider-posthog" -version = "0.1.129" +version = "0.1.130" description = "Official PostHog provider for the OpenFeature Python SDK." readme = "README.md" authors = [{ name = "PostHog", email = "engineering@posthog.com" }] diff --git a/posthog/CHANGELOG.md b/posthog/CHANGELOG.md index c8eb695a..063d59e0 100644 --- a/posthog/CHANGELOG.md +++ b/posthog/CHANGELOG.md @@ -1,5 +1,19 @@ # posthog +## 7.79.0 — 2026-10-28 + +### Minor changes + +- [bb2b9d6](https://github.com/posthog/posthog-python/commit/bb2b9d6bddd250f6dcf03be565af8ba94694e5db) Report flags that local evaluation could not resolve on `evaluate_flags()` snapshots. `FeatureFlagEvaluations.unresolved_flags` maps each such flag to an `UnresolvedFlagReason` (`experience_continuity`, `unsupported_definition`, `unresolved_dependency` or `missing_context`), and reading one reports the `local_evaluation_inconclusive` error instead of `flag_missing`. When definitions load, log one warning per flag that local evaluation can never resolve (experience continuity, a static cohort, an unknown operator, a malformed value, or a missing or circular flag dependency), and again only when its definition changes. `AsyncClient` evaluates only remotely, so its snapshots have no unresolved flags. + + Behavior changes: + + - A condition with a filter that local evaluation can't check and a filter that does not match now resolves locally as no match. Before, such a flag fell back to a remote request, or stayed unresolved with `only_evaluate_locally=True`. + - An inactive flag with experience continuity enabled now resolves to `false` locally instead of falling back to a remote request. + - A flag that depends on a flag with experience continuity enabled is no longer evaluated locally. It falls back to a remote request, or stays unresolved with `only_evaluate_locally=True`. + - On `$feature_flag_called` events from an `evaluate_flags()` snapshot, `$feature_flag_error` is now `local_evaluation_inconclusive` instead of `flag_missing` for a flag that has a loaded definition but that local evaluation could not resolve. + - `get_all_flags()`, `get_all_flags_and_payloads()` and `evaluate_flags()` no longer log an error with a traceback when a flag needs the server (for example, a static cohort). They fall back to a remote request without logging, as `get_feature_flag()` already did. — Thanks @gustavohstrassburger! + ## 7.78.0 — 2026-10-28 ### Minor changes diff --git a/posthog/version.py b/posthog/version.py index a1744501..c6c3571e 100644 --- a/posthog/version.py +++ b/posthog/version.py @@ -1 +1 @@ -VERSION = "7.78.0" +VERSION = "7.79.0" diff --git a/pyproject.toml b/pyproject.toml index d6533c0b..0b0b4b51 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "posthog" -version = "7.78.0" +version = "7.79.0" description = "Integrate PostHog into any python application." authors = [{ name = "PostHog", email = "engineering@posthog.com" }] maintainers = [{ name = "PostHog", email = "engineering@posthog.com" }] diff --git a/uv.lock b/uv.lock index e192a870..c0f3a3d1 100644 --- a/uv.lock +++ b/uv.lock @@ -2656,7 +2656,7 @@ wheels = [ [[package]] name = "openfeature-provider-posthog" -version = "0.1.129" +version = "0.1.130" source = { editable = "openfeature-provider" } dependencies = [ { name = "openfeature-sdk" }, @@ -3045,7 +3045,7 @@ wheels = [ [[package]] name = "posthog" -version = "7.78.0" +version = "7.79.0" source = { editable = "." } dependencies = [ { name = "distro" }, From 45ef69df43b891e92b07895ceb61fb2b0db5b9bf Mon Sep 17 00:00:00 2001 From: "releaser-posthog-python[bot]" <262414804+releaser-posthog-python[bot]@users.noreply.github.com> Date: Wed, 28 Oct 2026 21:22:09 +0000 Subject: [PATCH 37/60] Update generated references (cherry picked from commit 130f5a1e8508a7a9005bf46658ff8d43b7457232) --- .../posthog-python-references-7.79.0.json | 3548 +++++++++++++++++ .../posthog-python-references-latest.json | 6 +- 2 files changed, 3551 insertions(+), 3 deletions(-) create mode 100644 references/posthog-python-references-7.79.0.json diff --git a/references/posthog-python-references-7.79.0.json b/references/posthog-python-references-7.79.0.json new file mode 100644 index 00000000..a58eca35 --- /dev/null +++ b/references/posthog-python-references-7.79.0.json @@ -0,0 +1,3548 @@ +{ + "id": "posthog-python", + "hogRef": "0.3", + "info": { + "version": "7.79.0", + "id": "posthog-python", + "title": "PostHog Python SDK", + "description": "Integrate PostHog into any python application.", + "slugPrefix": "posthog-python", + "specUrl": "https://github.com/PostHog/posthog-python" + }, + "types": [ + { + "id": "FeatureFlag", + "name": "FeatureFlag", + "path": "posthog.types.FeatureFlag", + "properties": [ + { + "name": "key", + "type": "str", + "description": "Field: key" + }, + { + "name": "enabled", + "type": "bool", + "description": "Field: enabled" + }, + { + "name": "variant", + "type": "Optional[str]", + "description": "Field: variant" + }, + { + "name": "reason", + "type": "Optional[FlagReason]", + "description": "Field: reason" + }, + { + "name": "metadata", + "type": "Union[FlagMetadata, LegacyFlagMetadata]", + "description": "Field: metadata" + } + ], + "example": "" + }, + { + "id": "FeatureFlagResult", + "name": "FeatureFlagResult", + "path": "posthog.types.FeatureFlagResult", + "properties": [ + { + "name": "key", + "type": "str", + "description": "Field: key" + }, + { + "name": "enabled", + "type": "bool", + "description": "Field: enabled" + }, + { + "name": "variant", + "type": "Optional[str]", + "description": "Field: variant" + }, + { + "name": "payload", + "type": "Optional[Any]", + "description": "Field: payload" + }, + { + "name": "reason", + "type": "Optional[str]", + "description": "Field: reason" + } + ], + "example": "" + }, + { + "id": "FlagMetadata", + "name": "FlagMetadata", + "path": "posthog.types.FlagMetadata", + "properties": [ + { + "name": "id", + "type": "int", + "description": "Field: id" + }, + { + "name": "payload", + "type": "Optional[str]", + "description": "Field: payload" + }, + { + "name": "version", + "type": "int", + "description": "Field: version" + }, + { + "name": "description", + "type": "str", + "description": "Field: description" + }, + { + "name": "has_experiment", + "type": "Optional[bool]", + "description": "Field: has_experiment" + } + ], + "example": "" + }, + { + "id": "FlagReason", + "name": "FlagReason", + "path": "posthog.types.FlagReason", + "properties": [ + { + "name": "code", + "type": "str", + "description": "Field: code" + }, + { + "name": "condition_index", + "type": "Optional[int]", + "description": "Field: condition_index" + }, + { + "name": "description", + "type": "str", + "description": "Field: description" + } + ], + "example": "" + }, + { + "id": "FlagsAndPayloads", + "name": "FlagsAndPayloads", + "path": "posthog.types.FlagsAndPayloads", + "properties": [ + { + "name": "featureFlags", + "type": "Optional[dict[str, Union[bool, str]]]", + "description": "Field: featureFlags" + }, + { + "name": "featureFlagPayloads", + "type": "Optional[dict[str, Any]]", + "description": "Field: featureFlagPayloads" + } + ], + "example": "" + }, + { + "id": "FlagsResponse", + "name": "FlagsResponse", + "path": "posthog.types.FlagsResponse", + "properties": [ + { + "name": "flags", + "type": "dict[str, FeatureFlag]", + "description": "Field: flags" + }, + { + "name": "errorsWhileComputingFlags", + "type": "bool", + "description": "Field: errorsWhileComputingFlags" + }, + { + "name": "requestId", + "type": "str", + "description": "Field: requestId" + }, + { + "name": "quotaLimit", + "type": "Optional[list[str]]", + "description": "Field: quotaLimit" + }, + { + "name": "evaluatedAt", + "type": "Optional[int]", + "description": "Field: evaluatedAt" + }, + { + "name": "minimalFlagCalledEvents", + "type": "bool", + "description": "Field: minimalFlagCalledEvents" + } + ], + "example": "" + }, + { + "id": "LegacyFlagMetadata", + "name": "LegacyFlagMetadata", + "path": "posthog.types.LegacyFlagMetadata", + "properties": [ + { + "name": "payload", + "type": "Any", + "description": "Field: payload" + } + ], + "example": "" + }, + { + "id": "SendFeatureFlagsOptions", + "name": "SendFeatureFlagsOptions", + "path": "posthog.types.SendFeatureFlagsOptions", + "properties": [ + { + "name": "should_send", + "type": "bool", + "description": "Field: should_send" + }, + { + "name": "only_evaluate_locally", + "type": "Optional[bool]", + "description": "Field: only_evaluate_locally" + }, + { + "name": "person_properties", + "type": "Optional[dict[str, Any]]", + "description": "Field: person_properties" + }, + { + "name": "group_properties", + "type": "Optional[dict[str, dict[str, Any]]]", + "description": "Field: group_properties" + }, + { + "name": "flag_keys_filter", + "type": "Optional[list[str]]", + "description": "Field: flag_keys_filter" + } + ], + "example": "" + }, + { + "id": "OptionalCaptureArgs", + "name": "OptionalCaptureArgs", + "path": "posthog.args.OptionalCaptureArgs", + "properties": [ + { + "name": "distinct_id", + "type": "NotRequired[Union[Number, str, UUID, int, any]]", + "description": "Field: distinct_id" + }, + { + "name": "properties", + "type": "NotRequired[Optional[dict[str, Any]]]", + "description": "Field: properties" + }, + { + "name": "timestamp", + "type": "NotRequired[Union[datetime, str, any]]", + "description": "Field: timestamp" + }, + { + "name": "uuid", + "type": "NotRequired[Union[str, UUID, any]]", + "description": "Field: uuid" + }, + { + "name": "groups", + "type": "NotRequired[Optional[dict[str, str]]]", + "description": "Field: groups" + }, + { + "name": "flags", + "type": "NotRequired[Optional[ForwardRef('FeatureFlagEvaluations')]]", + "description": "Field: flags" + }, + { + "name": "send_feature_flags", + "type": "NotRequired[Union[bool, SendFeatureFlagsOptions, any]]", + "description": "Field: send_feature_flags" + }, + { + "name": "disable_geoip", + "type": "NotRequired[Optional[bool]]", + "description": "Field: disable_geoip" + }, + { + "name": "_property_allowlist", + "type": "NotRequired[Optional[frozenset[str]]]", + "description": "Field: _property_allowlist" + } + ], + "example": "" + }, + { + "id": "OptionalSetArgs", + "name": "OptionalSetArgs", + "path": "posthog.args.OptionalSetArgs", + "properties": [ + { + "name": "distinct_id", + "type": "NotRequired[Union[Number, str, UUID, int, any]]", + "description": "Field: distinct_id" + }, + { + "name": "properties", + "type": "NotRequired[Optional[dict[str, Any]]]", + "description": "Field: properties" + }, + { + "name": "timestamp", + "type": "NotRequired[Union[datetime, str, any]]", + "description": "Field: timestamp" + }, + { + "name": "uuid", + "type": "NotRequired[Union[str, UUID, any]]", + "description": "Field: uuid" + }, + { + "name": "disable_geoip", + "type": "NotRequired[Optional[bool]]", + "description": "Field: disable_geoip" + } + ], + "example": "" + }, + { + "id": "SendFeatureFlagsOptions", + "name": "SendFeatureFlagsOptions", + "path": "posthog.types.SendFeatureFlagsOptions", + "properties": [ + { + "name": "should_send", + "type": "bool", + "description": "Field: should_send" + }, + { + "name": "only_evaluate_locally", + "type": "Optional[bool]", + "description": "Field: only_evaluate_locally" + }, + { + "name": "person_properties", + "type": "Optional[dict[str, Any]]", + "description": "Field: person_properties" + }, + { + "name": "group_properties", + "type": "Optional[dict[str, dict[str, Any]]]", + "description": "Field: group_properties" + }, + { + "name": "flag_keys_filter", + "type": "Optional[list[str]]", + "description": "Field: flag_keys_filter" + } + ], + "example": "" + } + ], + "classes": [ + { + "id": "PostHog", + "title": "PostHog", + "description": "This is the SDK reference for the PostHog Python SDK. You can learn more about example usage in the [Python SDK documentation](/docs/libraries/python). You can also follow [Flask](/docs/libraries/flask) and [Django](/docs/libraries/django) guides to integrate PostHog into your project. For long-running applications, create one client during application startup and reuse it for the lifetime of the process. This keeps background queues predictable and makes shutdown flushing straightforward. Multiple clients are still supported for intentional multi-project or multi-host setups.", + "functions": [ + { + "id": "__init__", + "title": "Client", + "description": "Initialize a new PostHog client instance.", + "details": "", + "category": "Initialization", + "params": [ + { + "name": "project_api_key", + "description": "PostHog project API key/token.", + "isOptional": true, + "type": "str" + }, + { + "name": "host", + "description": "PostHog host. Defaults to the US ingestion endpoint when not set. App hosts such as ``https://us.posthog.com`` are mapped to the corresponding ingestion host.", + "isOptional": false, + "type": "any" + }, + { + "name": "debug", + "description": "Enable verbose SDK logging and re-raise errors from public API methods.", + "isOptional": false, + "type": "bool" + }, + { + "name": "max_queue_size", + "description": "Maximum number of events buffered before upload.", + "isOptional": false, + "type": "int" + }, + { + "name": "send", + "description": "If False, queueing succeeds but events are not sent.", + "isOptional": false, + "type": "bool" + }, + { + "name": "on_error", + "description": "Optional callback invoked by background consumers when an upload fails. Keep it short and non-blocking. Calling lifecycle methods directly is safe and deferred, but do not start another thread or task that calls ``flush()``, ``join()``, or ``shutdown()`` and then wait for it from the callback.", + "isOptional": false, + "type": "any" + }, + { + "name": "flush_at", + "description": "Number of queued events that triggers a batch upload.", + "isOptional": false, + "type": "int" + }, + { + "name": "flush_interval", + "description": "Maximum seconds a background consumer waits before flushing a partial batch.", + "isOptional": false, + "type": "float" + }, + { + "name": "gzip", + "description": "Whether to gzip event upload payloads.", + "isOptional": false, + "type": "bool" + }, + { + "name": "max_retries", + "description": "Number of upload retries. Values below 0 are treated as 0.", + "isOptional": false, + "type": "int" + }, + { + "name": "sync_mode", + "description": "If True, send each event synchronously instead of using background worker threads. This blocks the calling thread; in asyncio applications such as FastAPI, use ``AsyncPosthog`` instead.", + "isOptional": false, + "type": "bool" + }, + { + "name": "timeout", + "description": "HTTP request timeout in seconds for event uploads.", + "isOptional": false, + "type": "float" + }, + { + "name": "thread", + "description": "Number of background consumer threads.", + "isOptional": false, + "type": "int" + }, + { + "name": "poll_interval", + "description": "Seconds between local feature flag definition refreshes.", + "isOptional": false, + "type": "int" + }, + { + "name": "personal_api_key", + "description": "Deprecated alias for ``secret_key``. Still honored for backwards compatibility; prefer ``secret_key``, which also accepts a Project Secret API Key.", + "isOptional": false, + "type": "any" + }, + { + "name": "disabled", + "description": "If True, disable captures and API requests. Useful in tests.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable server-side GeoIP enrichment. Defaults to True.", + "isOptional": false, + "type": "bool" + }, + { + "name": "is_server", + "description": "Whether events are emitted from a server-side runtime. Defaults to True; set to False when using the SDK as a client/CLI so the device OS is attributed to the person normally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "historical_migration", + "description": "Mark events as historical migration imports.", + "isOptional": false, + "type": "bool" + }, + { + "name": "feature_flags_request_timeout_seconds", + "description": "Timeout in seconds for feature flag and remote config requests.", + "isOptional": false, + "type": "float" + }, + { + "name": "feature_flags_request_max_retries", + "description": "Number of retries for feature flag requests after network, transport, or timeout failures. Defaults to 1. Set to 0 to disable retries.", + "isOptional": false, + "type": "int" + }, + { + "name": "super_properties", + "description": "Properties merged into every captured event.", + "isOptional": false, + "type": "any" + }, + { + "name": "enable_exception_autocapture", + "description": "Automatically capture uncaught exceptions.", + "isOptional": false, + "type": "bool" + }, + { + "name": "log_captured_exceptions", + "description": "Also log exceptions captured by error tracking.", + "isOptional": false, + "type": "bool" + }, + { + "name": "project_root", + "description": "Root path used to determine in-app stack frames for captured exceptions. Defaults to the current working directory.", + "isOptional": false, + "type": "any" + }, + { + "name": "privacy_mode", + "description": "For AI observability, capture usage metadata without prompt inputs or outputs.", + "isOptional": false, + "type": "bool" + }, + { + "name": "before_send", + "description": "Optional callback that can modify or drop events before upload. Return ``None`` to drop an event.", + "isOptional": false, + "type": "any" + }, + { + "name": "flag_fallback_cache_url", + "description": "Optional feature flag fallback cache URL, such as ``memory://local/?ttl=300&size=10000`` or a Redis URL.", + "isOptional": false, + "type": "any" + }, + { + "name": "enable_local_evaluation", + "description": "Whether to poll feature flag definitions for local evaluation when a personal API key is configured.", + "isOptional": false, + "type": "bool" + }, + { + "name": "flag_definition_cache_provider", + "description": "Optional external cache provider for sharing feature flag definitions across workers.", + "isOptional": true, + "type": "FlagDefinitionCacheProvider" + }, + { + "name": "capture_exception_code_variables", + "description": "Capture local variable values on exception stack frames.", + "isOptional": false, + "type": "bool" + }, + { + "name": "code_variables_mask_patterns", + "description": "Variable-name patterns to mask when capturing code variables.", + "isOptional": false, + "type": "any" + }, + { + "name": "code_variables_ignore_patterns", + "description": "Variable-name patterns to omit when capturing code variables.", + "isOptional": false, + "type": "any" + }, + { + "name": "code_variables_mask_url_credentials", + "description": "Scrub credentials embedded in URLs/DSNs (e.g. ``user:pass@host``) from captured code variables, regardless of the surrounding variable name. Defaults to True.", + "isOptional": false, + "type": "any" + }, + { + "name": "code_variables_detect_secrets", + "description": "Last-resort entropy-based detection that redacts high-entropy secret-looking values (API keys, tokens, strong passwords) sitting in innocuously-named variables, after the name and URL checks. Skips structured ids (UUIDs, ObjectIds, hashes). Defaults to True.", + "isOptional": false, + "type": "any" + }, + { + "name": "in_app_modules", + "description": "Module/package prefixes treated as in-app frames in captured exceptions.", + "isOptional": false, + "type": "UnionType[list[str], any]" + }, + { + "name": "enable_exception_autocapture_rate_limiting", + "description": "Rate limit autocaptured exceptions client-side with a token bucket per exception type. Disabled by default.", + "isOptional": false, + "type": "bool" + }, + { + "name": "exception_autocapture_bucket_size", + "description": "Maximum burst of autocaptured exceptions allowed per exception type (token bucket size, clamped to 0-100).", + "isOptional": false, + "type": "int" + }, + { + "name": "exception_autocapture_refill_rate", + "description": "Tokens restored per refill interval for each exception type's bucket.", + "isOptional": false, + "type": "int" + }, + { + "name": "exception_autocapture_refill_interval_seconds", + "description": "Seconds between token refills for autocaptured exception rate limiting.", + "isOptional": false, + "type": "int" + }, + { + "name": "capture_mode", + "description": "Capture wire protocol to use. Defaults to ``CaptureMode.V0`` (legacy ``/batch/``). Set ``CaptureMode.V1`` (or pass the string ``\"v1\"``) to opt into ``/i/v1/analytics/events``. When omitted, the ``POSTHOG_CAPTURE_MODE`` env var is consulted, then ``V0``.", + "isOptional": false, + "type": "CaptureMode" + }, + { + "name": "capture_compression", + "description": "Request-body compression for capture-v1 uploads (ignored in V0, which uses ``gzip``). ``CaptureCompression.GZIP`` or ``DEFLATE`` (or the strings ``\"gzip\"``/``\"deflate\"``). When omitted, the ``POSTHOG_CAPTURE_COMPRESSION`` env var is consulted, then the legacy ``gzip`` flag, then no compression.", + "isOptional": false, + "type": "CaptureCompression" + }, + { + "name": "secret_key", + "description": "A Personal API Key or Project Secret API Key, used to authenticate local feature flag evaluation, remote config payloads, and decrypted flag payloads. Example:: posthog.Client(project_api_key, secret_key=\"phx_...\")", + "isOptional": false, + "type": "any" + }, + { + "name": "metrics", + "description": "", + "isOptional": true, + "type": "dict" + }, + { + "name": "enable_full_ai_capture", + "description": "Route PostHog AI wrapper events through the dedicated AI capture endpoint and capture full AI content: skips string truncation and passes media (base64/data URIs) through unredacted. ``privacy_mode`` always wins. Defaults to False.", + "isOptional": false, + "type": "bool" + }, + { + "name": "capture_trace_context", + "description": "When OpenTelemetry is installed and a valid span is active at capture time, add its trace and span IDs as ``$trace_id`` and ``$span_id`` properties to events captured with ``capture()`` and ``capture_ai()``, so they can be correlated with backend traces. Explicit ``$trace_id``/``$span_id`` values passed in ``properties`` win. Exception events (``capture_exception``) always attach these IDs regardless of this setting. Defaults to False.", + "isOptional": false, + "type": "bool" + }, + { + "name": "_use_ai_lane", + "description": "", + "isOptional": false, + "type": "bool" + }, + { + "name": "_enable_multimodal_capture", + "description": "", + "isOptional": false, + "type": "bool" + }, + { + "name": "traces", + "description": "Config dict for distributed tracing: ``service_name``, ``service_version``, ``environment``, ``resource_attributes``, ``flush_interval`` (5 s), ``max_queue_size`` (2048), ``max_export_batch_size`` (512), ``max_live_spans`` (10000), ``max_span_age`` (3600 s), ``max_attributes_per_span`` (128), ``max_events_per_span`` (128), ``max_attribute_value_length`` (8192). ``before_span_send`` is a callable, or a list run in order, that receives each finished span as a dict (``trace_id``, ``span_id`` and ``parent_span_id`` are read-only) and returns it, edited, or ``None`` to drop it; a hook that raises drops the span. Tracing is off until this is provided. Spans export on a background timer even with ``sync_mode``; serverless handlers should call ``flush()`` before returning. Defaults to None.", + "isOptional": true, + "type": "dict" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import Posthog\n\nposthog = Posthog('', host='')" + } + ] + }, + { + "id": "alias", + "title": "alias", + "description": "Create an alias between two distinct IDs.", + "details": "", + "category": "Identification", + "params": [ + { + "name": "previous_id", + "description": "The previous distinct ID. Required - the call is dropped with a warning if it is missing or empty.", + "isOptional": true, + "type": "Number" + }, + { + "name": "distinct_id", + "description": "The new distinct ID to alias to. Falls back to the context distinct ID; the call is dropped with a warning if neither is available.", + "isOptional": true, + "type": "str" + }, + { + "name": "timestamp", + "description": "The timestamp of the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC.", + "isOptional": false, + "type": "datetime" + }, + { + "name": "uuid", + "description": "A unique identifier for the event. If provided, it must be a valid UUID string or uuid.UUID instance; invalid values are ignored and replaced with a newly generated UUID.", + "isOptional": true, + "type": "str" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this event.", + "isOptional": true, + "type": "bool" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.alias(previous_id='distinct_id', distinct_id='alias_id')" + } + ] + }, + { + "id": "capture", + "title": "capture", + "description": "Captures an event manually. [Learn about capture best practices](https://posthog.com/docs/product-analytics/capture-events)", + "details": "", + "category": "Capture", + "params": [ + { + "name": "event", + "description": "The event name to capture.", + "isOptional": true, + "type": "str" + }, + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalCaptureArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Anonymous event", + "code": "# Anonymous event\nposthog.capture('some-anon-event')" + }, + { + "id": "example_2", + "name": "Context usage", + "code": "# Context usage\nfrom posthog import identify_context, new_context\nwith new_context():\n identify_context('distinct_id_of_the_user')\n posthog.capture('user_signed_up')\n posthog.capture('user_logged_in')\n posthog.capture('some-custom-action', distinct_id='distinct_id_of_the_user')" + }, + { + "id": "example_3", + "name": "Set event properties", + "code": "# Set event properties\nposthog.capture(\n \"user_signed_up\",\n distinct_id=\"distinct_id_of_the_user\",\n properties={\n \"login_type\": \"email\",\n \"is_free_trial\": \"true\"\n }\n)" + }, + { + "id": "example_4", + "name": "Page view event", + "code": "# Page view event\nposthog.capture('$pageview', distinct_id=\"distinct_id_of_the_user\", properties={'$current_url': 'https://example.com'})" + } + ] + }, + { + "id": "capture_ai", + "title": "capture_ai", + "description": "Capture an AI event on the dedicated AI capture endpoint. Beta: the signature is stable; operational limits (per-event size cap, batching, endpoint) may change without notice. Takes the same arguments and returns the same value as `capture()`: the event UUID, or None when the event was not admitted (disabled client, or dropped by `before_send`). The event is queued on an isolated AI lane with its own consumer pool and a higher per-event size cap, posting to the dedicated AI ingestion endpoint. The payload is sent as given \u2014 no redaction or truncation is applied here.", + "details": "", + "category": "Capture", + "params": [ + { + "name": "event", + "description": "", + "isOptional": true, + "type": "str" + }, + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalCaptureArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + } + }, + { + "id": "capture_exception", + "title": "capture_exception", + "description": "Capture an exception for error tracking. When OpenTelemetry is installed and a valid span is active, its trace and span IDs are added as ``$trace_id`` and ``$span_id`` event properties.", + "details": "", + "category": "Error Tracking", + "params": [ + { + "name": "exception", + "description": "The exception to capture.", + "isOptional": true, + "type": "BaseException" + }, + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalCaptureArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "try:\n # Some code that might fail\n pass\nexcept Exception as e:\n posthog.capture_exception(e, 'user_distinct_id', properties=additional_properties)" + } + ] + }, + { + "id": "evaluate_flags", + "title": "evaluate_flags", + "description": "Evaluate all feature flags for a user in a single call and return a :class:`FeatureFlagEvaluations` snapshot. Branch on ``.is_enabled()`` / ``.get_flag()`` and pass the same snapshot to :meth:`capture` via the ``flags`` option so events carry the exact flag values the code branched on. Prefer this over repeated ``get_feature_flag()`` calls and over ``capture(send_feature_flags=True)`` \u2014 it consolidates flag evaluation into a single ``/flags`` request per incoming request. Local evaluation is transparent: when the poller resolves a flag, the snapshot's ``$feature_flag_called`` events are tagged ``locally_evaluated=True`` and reason ``\"Evaluated locally\"``.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The user's distinct ID. If ``None``, falls back to the context distinct_id. If still unresolvable, returns an empty snapshot.", + "isOptional": false, + "type": "Number" + }, + { + "name": "groups", + "description": "Mapping of group type to group key.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "Person properties to use for evaluation.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "Group properties keyed by group type.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "If True, never fall back to remote evaluation \u2014 flags that can't be evaluated locally are omitted from the snapshot and listed with a reason in its ``unresolved_flags``.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup.", + "isOptional": true, + "type": "bool" + }, + { + "name": "flag_keys", + "description": "Optional list that scopes local evaluation, the underlying ``/flags`` request, and the returned snapshot. When omitted or ``None``, all flags are evaluated. An empty list returns an empty snapshot without evaluating flags. A requested key absent from loaded local definitions is included in one remote fallback per ``evaluate_flags`` call unless ``only_evaluate_locally`` is True. If the server also does not know the key, it is omitted from the snapshot.", + "isOptional": true, + "type": "list[str]" + }, + { + "name": "device_id", + "description": "Optional device ID override. If not provided, falls back to the context device_id (which may be set via tracing headers). Used by experience-continuity flags to match users across distinct_id changes.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "FeatureFlagEvaluations" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "flags = posthog.evaluate_flags(\n \"user_123\",\n person_properties={\"plan\": \"enterprise\"},\n)\nif flags.is_enabled(\"new-dashboard\"):\n render_new_dashboard()\nposthog.capture(\"page_viewed\", distinct_id=\"user_123\", flags=flags)" + } + ] + }, + { + "id": "feature_enabled", + "title": "feature_enabled", + "description": "Check if a feature flag is enabled for a user.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "The feature flag key.", + "isOptional": true, + "type": "str" + }, + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to only evaluate locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "send_feature_flag_events", + "description": "Whether to send feature flag events.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[bool]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "is_my_flag_enabled = posthog.feature_enabled('flag-key', 'distinct_id_of_your_user')\nif is_my_flag_enabled:\n # Do something differently for this user\n # Optional: fetch the payload\n matched_flag_payload = posthog.get_feature_flag_payload('flag-key', 'distinct_id_of_your_user')" + } + ] + }, + { + "id": "feature_flag_definitions", + "title": "feature_flag_definitions", + "description": "Return feature flag definitions loaded for local evaluation. Returns: The currently loaded feature flag definitions, or ``None`` before local evaluation has loaded definitions.", + "details": "", + "category": "Feature flags", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "flush", + "title": "flush", + "description": "Attempt immediate delivery of queued events and ended spans, keeping the client reusable. For a client shared across requests or serverless invocations (including the module-level client), call ``flush()`` before a short-lived invocation returns. Use ``shutdown()`` instead only for final process cleanup or when disposing of a client that will not be reused. Flush does not stop the background workers or feature flag poller, and does not end open spans. Returning does not guarantee server receipt: pending work can remain after a timeout or span export failure, and failed events may be dropped by the consumers. Calls made directly from SDK callbacks such as ``on_error`` are deferred rather than blocking the worker that invoked the callback.", + "details": "", + "category": null, + "params": [ + { + "name": "timeout_seconds", + "description": "Seconds to wait for event queues to drain, shared across event lanes. Defaults to 10 seconds. Pass ``None`` to wait without a time limit; this does not guarantee successful delivery. Queued spans flush concurrently. The span exporter waits up to this many seconds for an in-flight flush, then uses a fresh drain budget once it acquires the lock. If the lock wait times out, the in-flight flush is left to finish. At least one span request is attempted after acquiring the lock, even with no budget left; retriable failures are retried after backoff while budget remains, with one last attempt at the deadline. Each span request is bounded by the client's ``timeout`` in seconds, and the last request is not cut short. This is therefore not a strict wall-clock limit for the whole call. With ``None``, a retriable span failure is left for a later flush rather than retried within this call.", + "isOptional": true, + "type": "float" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.capture('event_name', distinct_id='user_id')\n# Before a serverless invocation returns; keep the shared client open.\nposthog.flush(timeout_seconds=10)" + } + ] + }, + { + "id": "get_active_span", + "title": "get_active_span", + "description": "The span that is active in the current context, or ``None``. Alpha. Only entering a span (``with posthog.start_span(...) as span:``) makes it active; a span started manually is not. Use it to propagate the trace to the next service: ``span.traceparent()`` is the header value.", + "details": "", + "category": "Tracing", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[Span]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "span = posthog.get_active_span()\nif span is not None:\n headers[\"traceparent\"] = span.traceparent()" + } + ] + }, + { + "id": "get_all_flags", + "title": "get_all_flags", + "description": "Get all feature flags for a user.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to only evaluate locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "flag_keys_to_evaluate", + "description": "A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance.", + "isOptional": true, + "type": "list[str]" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[dict[str, Union[bool, str]]]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.get_all_flags('distinct_id_of_your_user')" + } + ] + }, + { + "id": "get_all_flags_and_payloads", + "title": "get_all_flags_and_payloads", + "description": "Get all feature flags and their payloads for a user.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to only evaluate locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "flag_keys_to_evaluate", + "description": "A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance.", + "isOptional": true, + "type": "list[str]" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "FlagsAndPayloads" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.get_all_flags_and_payloads('distinct_id_of_your_user')" + } + ] + }, + { + "id": "get_feature_flag", + "title": "get_feature_flag", + "description": "Get multivariate feature flag value for a user.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "The feature flag key.", + "isOptional": true, + "type": "str" + }, + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to only evaluate locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "send_feature_flag_events", + "description": "Whether to send feature flag events.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Union[bool, str, any]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "enabled_variant = posthog.get_feature_flag('flag-key', 'distinct_id_of_your_user')\nif enabled_variant == 'variant-key': # replace 'variant-key' with the key of your variant\n # Do something differently for this user\n # Optional: fetch the payload\n matched_flag_payload = posthog.get_feature_flag_payload('flag-key', 'distinct_id_of_your_user')" + } + ] + }, + { + "id": "get_feature_flag_evaluation_runtime", + "title": "get_feature_flag_evaluation_runtime", + "description": "Return where a locally loaded feature flag is meant to be evaluated.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "The feature flag key.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[FeatureFlagEvaluationRuntime]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import FeatureFlagEvaluationRuntime\n\nruntime = posthog.get_feature_flag_evaluation_runtime(\"my-flag\")\nif runtime is FeatureFlagEvaluationRuntime.SERVER:\n ..." + } + ] + }, + { + "id": "get_feature_flag_keys_by_evaluation_runtime", + "title": "get_feature_flag_keys_by_evaluation_runtime", + "description": "Return the keys of locally loaded flags that a runtime can evaluate. A flag set to ``FeatureFlagEvaluationRuntime.ALL`` suits either runtime, so it is returned for ``CLIENT`` and for ``SERVER``, and asking for ``ALL`` returns every loaded flag. Use this to decide which flags to hand to a browser when a backend serves flags to its own frontend.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "evaluation_runtime", + "description": "The runtime to match, as a ``FeatureFlagEvaluationRuntime`` or its string value.", + "isOptional": true, + "type": "FeatureFlagEvaluationRuntime" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "list[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import FeatureFlagEvaluationRuntime\n\nclient_keys = posthog.get_feature_flag_keys_by_evaluation_runtime(\n FeatureFlagEvaluationRuntime.CLIENT\n)" + } + ] + }, + { + "id": "get_feature_flag_payload", + "title": "get_feature_flag_payload", + "description": "Get the payload for a feature flag.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "The feature flag key.", + "isOptional": true, + "type": "str" + }, + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "match_value", + "description": "The specific flag value to get payload for.", + "isOptional": false, + "type": "bool" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to only evaluate locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "send_feature_flag_events", + "description": "Deprecated. Use get_feature_flag() instead if you need events.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[object]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "is_my_flag_enabled = posthog.feature_enabled('flag-key', 'distinct_id_of_your_user')\n\nif is_my_flag_enabled:\n # Do something differently for this user\n # Optional: fetch the payload\n matched_flag_payload = posthog.get_feature_flag_payload('flag-key', 'distinct_id_of_your_user')" + } + ] + }, + { + "id": "get_feature_flag_result", + "title": "get_feature_flag_result", + "description": "Get a FeatureFlagResult object which contains the flag result and payload for a key by evaluating locally or remotely depending on whether local evaluation is enabled and the flag can be locally evaluated. This also captures the `$feature_flag_called` event unless `send_feature_flag_events` is `False`.", + "details": "", + "category": null, + "params": [ + { + "name": "key", + "description": "The feature flag key.", + "isOptional": true, + "type": "str" + }, + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to only evaluate locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "send_feature_flag_events", + "description": "Whether to send feature flag events.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[FeatureFlagResult]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "flag_result = posthog.get_feature_flag_result('flag-key', 'distinct_id_of_your_user')\nif flag_result and flag_result.get_value() == 'variant-key':\n # Do something differently for this user\n # Optional: fetch the payload\n matched_flag_payload = flag_result.payload" + } + ] + }, + { + "id": "get_feature_flags_and_payloads", + "title": "get_feature_flags_and_payloads", + "description": "Get feature flags and payloads for a user.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "flag_keys_to_evaluate", + "description": "A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance.", + "isOptional": true, + "type": "list[str]" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "FlagsAndPayloads" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "result = posthog.get_feature_flags_and_payloads('')" + } + ] + }, + { + "id": "get_feature_payloads", + "title": "get_feature_payloads", + "description": "Get feature flag payloads for a user, preserving valid serialized JSON.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "flag_keys_to_evaluate", + "description": "A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance.", + "isOptional": true, + "type": "list[str]" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "dict[str, Optional[str]]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "payloads = posthog.get_feature_payloads('')" + } + ] + }, + { + "id": "get_feature_variants", + "title": "get_feature_variants", + "description": "Get feature flag variants for a user.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "flag_keys_to_evaluate", + "description": "A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance.", + "isOptional": true, + "type": "list[str]" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "dict[str, Union[bool, str]]" + } + }, + { + "id": "get_flags_decision", + "title": "get_flags_decision", + "description": "Get feature flags decision.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": false, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "flag_keys_to_evaluate", + "description": "A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance.", + "isOptional": true, + "type": "list[str]" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "FlagsResponse" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "decision = posthog.get_flags_decision('user123')" + } + ] + }, + { + "id": "get_remote_config_payload", + "title": "get_remote_config_payload", + "description": "Get the payload for a remote config feature flag.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "The remote config feature flag key.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "get_tags", + "title": "get_tags", + "description": "Get all tags from the current context. Returns: Dict of all tags in the current context.", + "details": "", + "category": "Contexts", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "dict[str, Any]" + } + }, + { + "id": "group_identify", + "title": "group_identify", + "description": "Identify a group and set its properties.", + "details": "", + "category": "Identification", + "params": [ + { + "name": "group_type", + "description": "The type of group (e.g., 'company', 'team'). Required - the call is dropped with a warning if it is missing or empty.", + "isOptional": true, + "type": "str" + }, + { + "name": "group_key", + "description": "The unique identifier for the group. Required - the call is dropped with a warning if it is missing or empty.", + "isOptional": true, + "type": "str" + }, + { + "name": "properties", + "description": "A dictionary of properties to set on the group.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "timestamp", + "description": "The timestamp of the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC.", + "isOptional": false, + "type": "datetime" + }, + { + "name": "uuid", + "description": "A unique identifier for the event. If provided, it must be a valid UUID string or uuid.UUID instance; invalid values are ignored and replaced with a newly generated UUID.", + "isOptional": false, + "type": "str" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this event.", + "isOptional": true, + "type": "bool" + }, + { + "name": "distinct_id", + "description": "The distinct ID of the user performing the action.", + "isOptional": false, + "type": "Number" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.group_identify('company', 'company_id_in_your_db', {\n 'name': 'Awesome Inc.',\n 'employees': 11\n})" + } + ] + }, + { + "id": "identify_context", + "title": "identify_context", + "description": "Identify the current context with a distinct ID.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "distinct_id", + "description": "The distinct ID to associate with the current context and its children.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + } + }, + { + "id": "join", + "title": "join", + "description": "Attempt to process queued events and end the consumer threads. Do not use directly, call `shutdown()` instead. Failed or undrainable events may be dropped and reported through logging or ``on_error``; returning does not guarantee server receipt. Lifecycle cleanup is attempted once, and cleanup failures are logged without retry.", + "details": "", + "category": null, + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.join()" + } + ] + }, + { + "id": "load_feature_flags", + "title": "load_feature_flags", + "description": "Load feature flags for local evaluation.", + "details": "", + "category": "Feature flags", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.load_feature_flags()" + } + ] + }, + { + "id": "new_context", + "title": "new_context", + "description": "Create a new context for managing shared state. Learn more about [contexts](/docs/libraries/python#contexts).", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "fresh", + "description": "Whether to create a fresh context that doesn't inherit from parent.", + "isOptional": false, + "type": "bool" + }, + { + "name": "capture_exceptions", + "description": "Whether to automatically capture exceptions in this context. If omitted, defaults to this client's exception autocapture setting.", + "isOptional": true, + "type": "bool" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "with client.new_context():\n client.identify_context('')\n client.capture('event_name')" + } + ] + }, + { + "id": "scoped", + "title": "scoped", + "description": "Decorator that creates a new context for the wrapped function using this client.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "fresh", + "description": "Whether to create a fresh context that doesn't inherit from parent.", + "isOptional": false, + "type": "bool" + }, + { + "name": "capture_exceptions", + "description": "Whether to automatically capture exceptions in this context. If omitted, defaults to this client's exception autocapture setting.", + "isOptional": true, + "type": "bool" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "set", + "title": "set", + "description": "Set properties on a person profile.", + "details": "", + "category": "Identification", + "params": [ + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalSetArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Set with distinct id", + "code": "# Set with distinct id\nposthog.set(distinct_id='user123', properties={'name': 'Max Hedgehog'})" + } + ] + }, + { + "id": "set_context_device_id", + "title": "set_context_device_id", + "description": "Set the device ID for the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "device_id", + "description": "The device ID to associate with the current context and its children.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + } + }, + { + "id": "set_context_session", + "title": "set_context_session", + "description": "Set the session ID for the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "session_id", + "description": "The session ID to associate with the current context and its children.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + } + }, + { + "id": "set_once", + "title": "set_once", + "description": "Set properties on a person profile only if they haven't been set before.", + "details": "", + "category": "Identification", + "params": [ + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalSetArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.set_once(distinct_id='user123', properties={'initial_signup_date': '2024-01-01'})" + } + ] + }, + { + "id": "shutdown", + "title": "shutdown", + "description": "Attempt final delivery and permanently shut down the client. Call this for final process cleanup or when disposing of a client that will not be reused. It closes capture lanes, stops background workers and the feature flag poller, and tears down integrations. Do not call it at the end of each request or serverless invocation when the client is shared; call ``flush()`` before returning instead. A shut-down client cannot be reused; create a new client if needed. Normally this method blocks until queued events have been attempted and cleanup finishes. Failed or undrainable events may be dropped and reported through logging or ``on_error``; returning does not guarantee server receipt. Event draining has no timeout. Queued spans get one final flush with up to 30 seconds waiting for an in-flight flush, then a fresh 30-second drain budget; the last HTTP request is not cut short. Any spans it cannot send are discarded with a warning, as are spans still open. Lifecycle cleanup is attempted once, and cleanup failures are logged without retry. When called directly from an SDK callback such as ``on_error``, shutdown is deferred to avoid blocking the worker that invoked the callback. If the callback must coordinate a blocking shutdown, have it signal an application-owned thread and return before that thread calls shutdown. Do not wait inside the callback for another thread or task that calls a lifecycle method.", + "details": "", + "category": null, + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.shutdown()" + } + ] + }, + { + "id": "start_span", + "title": "start_span", + "description": "Start a span for distributed tracing. Alpha. Returns a span handle. Use it as a context manager to make it the active span for the block and end it on exit (recording a raised exception on the way out); or call ``end()`` yourself for a span that cannot wrap a block. Spans started inside the block nest under it automatically. Always returns a usable handle, even when tracing is off, so calling code never branches.", + "details": "", + "category": "Tracing", + "params": [ + { + "name": "name", + "description": "A low-cardinality operation name, e.g. ``GET /users/:id``. Variable values belong in attributes, not the name.", + "isOptional": true, + "type": "str" + }, + { + "name": "kind", + "description": "``internal`` (default), ``server``, ``client``, ``producer`` or ``consumer``.", + "isOptional": true, + "type": "str" + }, + { + "name": "attributes", + "description": "Initial attributes.", + "isOptional": true, + "type": "Mapping[str, Any]" + }, + { + "name": "parent", + "description": "A span handle, or an inbound W3C ``traceparent`` header value to continue a remote trace. Defaults to the active span. A forked child starts with no active span; pass the parent span to continue a trace across a fork.", + "isOptional": false, + "type": "Span" + }, + { + "name": "tracestate", + "description": "The inbound ``tracestate`` header accompanying a ``traceparent`` string ``parent``; preserved and propagated.", + "isOptional": true, + "type": "str" + }, + { + "name": "start_time", + "description": "A ``datetime`` or epoch seconds, to backdate the span.", + "isOptional": false, + "type": "datetime" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Span" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog = Posthog(\"\", traces={\"service_name\": \"checkout-api\"})\n\nwith posthog.start_span(\"POST /checkout\", parent=request.headers.get(\"traceparent\")) as span:\n span.set_attribute(\"plan\", user.plan)\n with posthog.start_span(\"db.query\", kind=\"client\"):\n ...\n outgoing_headers = {\"traceparent\": span.traceparent()}" + } + ] + }, + { + "id": "tag", + "title": "tag", + "description": "Add a tag to the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "name", + "description": "The tag key.", + "isOptional": true, + "type": "str" + }, + { + "name": "value", + "description": "The tag value.", + "isOptional": true, + "type": "Any" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + } + } + ] + }, + { + "id": "PostHogModule", + "title": "PostHog Module Functions", + "description": "Global functions available in the PostHog module", + "functions": [ + { + "id": "alias", + "title": "alias", + "description": "Associate user behaviour before and after they e.g. register, login, or perform some other identifying action.", + "details": "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?\". Particularly useful for associating user behaviour before and after they e.g. register, login, or perform some other identifying action.", + "category": "Identification", + "params": [ + { + "name": "previous_id", + "description": "The unique ID of the user before", + "isOptional": true, + "type": "Number" + }, + { + "name": "distinct_id", + "description": "The current unique id", + "isOptional": true, + "type": "str" + }, + { + "name": "timestamp", + "description": "Optional timestamp for the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC.", + "isOptional": false, + "type": "datetime" + }, + { + "name": "uuid", + "description": "Optional UUID for the event", + "isOptional": true, + "type": "str" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup", + "isOptional": true, + "type": "bool" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Alias user", + "code": "# Alias user\nfrom posthog import alias\nalias(previous_id='distinct_id', distinct_id='alias_id')" + } + ] + }, + { + "id": "capture", + "title": "capture", + "description": "Capture anything a user does within your system.", + "details": "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. A capture call requires an event name to specify the event. We recommend using [verb] [noun], like `movie played` or `movie updated` to easily identify what your events mean later on. Capture takes a number of optional arguments, which are defined by the `OptionalCaptureArgs` type.", + "category": "Events", + "params": [ + { + "name": "event", + "description": "The event name to specify the event **kwargs: Optional arguments including:", + "isOptional": true, + "type": "str" + }, + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalCaptureArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Context and capture usage", + "code": "# Context and capture usage\nfrom posthog import new_context, identify_context, tag_context, capture\n# Enter a new context (e.g. a request/response cycle, an instance of a background job, etc)\nwith new_context():\n # Associate this context with some user, by distinct_id\n identify_context('some user')\n\n # Capture an event, associated with the context-level distinct ID ('some user')\n capture('movie started')\n\n # Capture an event associated with some other user (overriding the context-level distinct ID)\n capture('movie joined', distinct_id='some-other-user')\n\n # Capture an event with some properties\n capture('movie played', properties={'movie_id': '123', 'category': 'romcom'})\n\n # Capture an event with some properties\n capture('purchase', properties={'product_id': '123', 'category': 'romcom'})\n # Capture an event with some associated group\n capture('purchase', groups={'company': 'id:5'})\n\n # Adding a tag to the current context will cause it to appear on all subsequent events\n tag_context('some-tag', 'some-value')\n\n capture('another-event') # Will be captured with `'some-tag': 'some-value'` in the properties dict" + }, + { + "id": "example_2", + "name": "Set event properties", + "code": "# Set event properties\nfrom posthog import capture\ncapture(\n \"user_signed_up\",\n distinct_id=\"distinct_id_of_the_user\",\n properties={\n \"login_type\": \"email\",\n \"is_free_trial\": \"true\"\n }\n)" + } + ] + }, + { + "id": "capture_ai", + "title": "capture_ai", + "description": "Capture an AI event on the dedicated AI capture endpoint. Beta: the signature is stable; operational limits (per-event size cap, batching, endpoint) may change without notice. Takes the same arguments and returns the same value as `capture()`: the event UUID, or None when the event was not admitted (disabled client, or dropped by `before_send`). The event is delivered on an isolated queue with its own consumer pool and a higher per-event size cap, posting to the dedicated AI ingestion endpoint. The payload is sent as given \u2014 no redaction or truncation is applied here.", + "details": "", + "category": "Events", + "params": [ + { + "name": "event", + "description": "The event name, normally one of the `$ai_*` event names. **kwargs: Same optional arguments as `capture()`.", + "isOptional": true, + "type": "str" + }, + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalCaptureArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import capture_ai\n\nuuid = capture_ai(\n \"$ai_generation\",\n distinct_id=\"user_123\",\n properties={\"$ai_model\": \"gpt-5\"},\n)" + } + ] + }, + { + "id": "capture_exception", + "title": "capture_exception", + "description": "Capture exceptions that happen in your code.", + "details": "Capture exception is idempotent - if it is called twice with the same exception instance, only a occurrence will be tracked in posthog. This is because, generally, contexts will cause exceptions to be captured automatically. However, to ensure you track an exception, if you catch and do not re-raise it, capturing it manually is recommended, unless you are certain it will have crossed a context boundary (e.g. by existing a `with posthog.new_context():` block already). If the passed exception was raised and caught, the captured stack trace will consist of every frame between where the exception was raised and the point at which it is captured (the \"traceback\"). If the passed exception was never raised, e.g. if you call `posthog.capture_exception(ValueError(\"Some Error\"))`, the stack trace captured will be the full stack trace at the moment the exception was captured. Note that heavy use of contexts will lead to truncated stack traces, as the exception will be captured by the context entered most recently, 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. `capture_exception` takes the same set of optional arguments as `capture`.", + "category": "Events", + "params": [ + { + "name": "exception", + "description": "The exception to capture. If not provided, the current exception is captured via `sys.exc_info()` **kwargs: Optional capture arguments including distinct_id, properties, timestamp, uuid, groups, flags, send_feature_flags, and disable_geoip. Overriding reserved exception properties through ``properties`` is deprecated and will stop working in the next major version.", + "isOptional": false, + "type": "BaseException" + }, + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalCaptureArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Capture exception", + "code": "# Capture exception\nfrom posthog import capture_exception\ntry:\n risky_operation()\nexcept Exception as e:\n capture_exception(e)" + } + ] + }, + { + "id": "evaluate_flags", + "title": "evaluate_flags", + "description": "Evaluate all feature flags for a user in a single call and return a :class:`FeatureFlagEvaluations` snapshot. Branch on ``.is_enabled()`` / ``.get_flag()`` and pass the same snapshot to ``capture()`` via the ``flags`` option so events carry the exact flag values the code branched on. Prefer this over repeated ``get_feature_flag()`` calls and over ``capture(send_feature_flags=True)`` \u2014 it consolidates flag evaluation into a single ``/flags`` request per incoming request.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The user's distinct ID. If ``None``, falls back to the context distinct_id. If still unresolvable, returns an empty snapshot.", + "isOptional": false, + "type": "Number" + }, + { + "name": "groups", + "description": "Mapping of group type to group key.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "Person properties to use for evaluation.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "Group properties keyed by group type.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "If ``True``, never fall back to remote evaluation and omit flags that cannot be evaluated locally. The snapshot's ``unresolved_flags`` lists them with a reason.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup.", + "isOptional": true, + "type": "bool" + }, + { + "name": "flag_keys", + "description": "Optional list that scopes local evaluation, the underlying ``/flags`` request, and the returned snapshot. When omitted or ``None``, all flags are evaluated. An empty list returns an empty snapshot without evaluating flags. A requested key absent from loaded local definitions is included in one remote fallback per ``evaluate_flags`` call unless ``only_evaluate_locally`` is ``True``. If the server also does not know the key, it is omitted from the snapshot.", + "isOptional": true, + "type": "list[str]" + }, + { + "name": "device_id", + "description": "Optional device ID override. If not provided, falls back to the context device_id (which may be set via tracing headers). Used by experience-continuity flags to match users across distinct_id changes.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "FeatureFlagEvaluations" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import evaluate_flags, capture\nflags = evaluate_flags(\"user_123\", person_properties={\"plan\": \"enterprise\"})\nif flags.is_enabled(\"new-dashboard\"):\n render_new_dashboard()\ncapture(\"page_viewed\", distinct_id=\"user_123\", flags=flags)" + } + ] + }, + { + "id": "feature_enabled", + "title": "feature_enabled", + "description": "Use feature flags to enable or disable features for users.", + "details": "You can call `posthog.load_feature_flags()` before to make sure you're not doing unexpected requests.", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "The feature flag key", + "isOptional": true, + "type": "str" + }, + { + "name": "distinct_id", + "description": "The user's distinct ID", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "Groups mapping", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "Person properties", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "Group properties", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to evaluate only locally", + "isOptional": false, + "type": "bool" + }, + { + "name": "send_feature_flag_events", + "description": "Whether to send feature flag events", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "Optional device ID override for experience-continuity flags", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[bool]" + }, + "examples": [ + { + "id": "example_1", + "name": "Boolean feature flag", + "code": "# Boolean feature flag\nfrom posthog import feature_enabled, get_feature_flag_payload\nis_my_flag_enabled = feature_enabled('flag-key', 'distinct_id_of_your_user')\nif is_my_flag_enabled:\n matched_flag_payload = get_feature_flag_payload('flag-key', 'distinct_id_of_your_user')" + } + ] + }, + { + "id": "feature_flag_definitions", + "title": "feature_flag_definitions", + "description": "Returns loaded feature flags.", + "details": "Returns loaded feature flags, if any. Helpful for debugging what flag information you have loaded.", + "category": "Feature flags", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import feature_flag_definitions\ndefinitions = feature_flag_definitions()" + } + ] + }, + { + "id": "flush", + "title": "flush", + "description": "Attempt immediate delivery of queued events and ended spans, keeping the global client reusable. Call ``flush()`` before a short-lived request or serverless invocation returns when using the shared module-level client. Use ``shutdown()`` only for final process cleanup or a client that will not be reused. Flush does not stop the background workers or feature flag poller, and does not end open spans. Returning does not guarantee server receipt: pending work can remain after a timeout or span export failure, and failed events may be dropped by consumers. Calls made directly from SDK callbacks such as ``on_error`` are deferred rather than blocking the worker that invoked the callback.", + "details": "", + "category": "Client management", + "params": [ + { + "name": "timeout_seconds", + "description": "Seconds to wait for event queues to drain, shared across event lanes. Defaults to 10 seconds. Pass ``None`` to wait without a time limit; this does not guarantee successful delivery. Queued spans flush concurrently, waiting up to this many seconds for an in-flight flush before starting a fresh drain budget. A lock wait timeout leaves the in-flight flush to finish. At least one span request is attempted after acquiring the lock, even with no budget left; retriable failures are retried after backoff while budget remains, with one last attempt at the deadline. The last span request is not cut short and is bounded by the client's ``timeout`` in seconds, so this is not a strict wall-clock limit for the whole call. With ``None``, a retriable span failure is left for a later flush rather than retried within this call.", + "isOptional": true, + "type": "float" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "import posthog\nposthog.capture('event_name', distinct_id='user_id')\n# Before a serverless invocation returns; keep the global client open.\nposthog.flush(timeout_seconds=10)" + } + ] + }, + { + "id": "get_active_span", + "title": "get_active_span", + "description": "The span that is active in the current context, or ``None``. Alpha. Only entering a span (``with posthog.start_span(...) as span:``) makes it active; a span started manually is not. Use it to propagate the trace to the next service: ``span.traceparent()`` is the header value.", + "details": "", + "category": "Tracing", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[Span]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "span = posthog.get_active_span()\nif span is not None:\n headers[\"traceparent\"] = span.traceparent()" + } + ] + }, + { + "id": "get_all_flags", + "title": "get_all_flags", + "description": "Get all flags for a given user.", + "details": "Flags are key-value pairs where the key is the flag key and the value is the flag variant, or True, or False.", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The user's distinct ID", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "Groups mapping", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "Person properties", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "Group properties", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to evaluate only locally", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "Optional device ID override for experience-continuity flags", + "isOptional": true, + "type": "str" + }, + { + "name": "flag_keys_to_evaluate", + "description": "Optional list of flag keys to evaluate (evaluates all if None)", + "isOptional": true, + "type": "list[str]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[dict[str, Union[bool, str]]]" + }, + "examples": [ + { + "id": "example_1", + "name": "All flags for user", + "code": "# All flags for user\nfrom posthog import get_all_flags\nget_all_flags('distinct_id_of_your_user')" + } + ] + }, + { + "id": "get_all_flags_and_payloads", + "title": "get_all_flags_and_payloads", + "description": "Get all feature flag values and payloads for a user.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The user's distinct ID.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "Mapping of group type to group key.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "Person properties to use for evaluation.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "Group properties keyed by group type.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to evaluate only locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup.", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "Optional device ID override for experience-continuity flags.", + "isOptional": true, + "type": "str" + }, + { + "name": "flag_keys_to_evaluate", + "description": "Optional list of flag keys to evaluate. Evaluates all flags when omitted.", + "isOptional": true, + "type": "list[str]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "FlagsAndPayloads" + } + }, + { + "id": "get_feature_flag", + "title": "get_feature_flag", + "description": "Get feature flag variant for users. Used with experiments.", + "details": "`groups` are a mapping from group type to group key. So, if you have a group type of \"organization\" and a group key of \"5\", you would pass groups={\"organization\": \"5\"}. `group_properties` take the format: { group_type_name: { group_properties } }. So, for example, if you have the group type \"organization\" and the group key \"5\", with the properties name, and employee count, you'll send these as: group_properties={\"organization\": {\"name\": \"PostHog\", \"employees\": 11}}.", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "The feature flag key", + "isOptional": true, + "type": "str" + }, + { + "name": "distinct_id", + "description": "The user's distinct ID", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "Groups mapping from group type to group key", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "Person properties", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "Group properties in format { group_type_name: { group_properties } }", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to evaluate only locally", + "isOptional": false, + "type": "bool" + }, + { + "name": "send_feature_flag_events", + "description": "Whether to send feature flag events", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "Optional device ID override for experience-continuity flags", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Union[bool, str, any]" + }, + "examples": [ + { + "id": "example_1", + "name": "Multivariate feature flag", + "code": "# Multivariate feature flag\nfrom posthog import get_feature_flag, get_feature_flag_payload\nenabled_variant = get_feature_flag('flag-key', 'distinct_id_of_your_user')\nif enabled_variant == 'variant-key':\n matched_flag_payload = get_feature_flag_payload('flag-key', 'distinct_id_of_your_user')" + } + ] + }, + { + "id": "get_feature_flag_evaluation_runtime", + "title": "get_feature_flag_evaluation_runtime", + "description": "Return where a locally loaded feature flag is meant to be evaluated.", + "details": "Reads the `evaluation_runtime` each flag definition carries, so no extra request is made. Returns `None` when local evaluation has not loaded a definition for this key. A definition that carries no runtime reports `FeatureFlagEvaluationRuntime.ALL`, the default PostHog applies.", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[FeatureFlagEvaluationRuntime]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import FeatureFlagEvaluationRuntime, get_feature_flag_evaluation_runtime\nruntime = get_feature_flag_evaluation_runtime(\"my-flag\")" + } + ] + }, + { + "id": "get_feature_flag_keys_by_evaluation_runtime", + "title": "get_feature_flag_keys_by_evaluation_runtime", + "description": "Return the keys of locally loaded flags that a runtime can evaluate.", + "details": "A flag set to `FeatureFlagEvaluationRuntime.ALL` suits either runtime, so it is returned for `CLIENT` and for `SERVER`. Use this to decide which flags to hand to a browser when a backend serves flags to its own frontend.", + "category": "Feature flags", + "params": [ + { + "name": "evaluation_runtime", + "description": "", + "isOptional": true, + "type": "FeatureFlagEvaluationRuntime" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "list[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import FeatureFlagEvaluationRuntime, get_feature_flag_keys_by_evaluation_runtime\nclient_keys = get_feature_flag_keys_by_evaluation_runtime(FeatureFlagEvaluationRuntime.CLIENT)" + } + ] + }, + { + "id": "get_feature_flag_payload", + "title": "get_feature_flag_payload", + "description": "Get the payload associated with a feature flag value. Deprecated for new code. Prefer ``evaluate_flags()`` and ``flags.get_flag_payload(key)`` so flag evaluation happens once per request.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "The feature flag key.", + "isOptional": true, + "type": "str" + }, + { + "name": "distinct_id", + "description": "The user's distinct ID.", + "isOptional": true, + "type": "Number" + }, + { + "name": "match_value", + "description": "Optional flag value to use when selecting a payload.", + "isOptional": false, + "type": "bool" + }, + { + "name": "groups", + "description": "Mapping of group type to group key.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "Person properties to use for evaluation.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "Group properties keyed by group type.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to evaluate only locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "send_feature_flag_events", + "description": "Whether to send a $feature_flag_called event.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup.", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "Optional device ID override for experience-continuity flags.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[object]" + } + }, + { + "id": "get_feature_flag_result", + "title": "get_feature_flag_result", + "description": "Get a FeatureFlagResult object which contains the flag result and payload. This method evaluates a feature flag and returns a FeatureFlagResult object containing: - enabled: Whether the flag is enabled - variant: The variant value if the flag has variants - payload: The payload associated with the flag (automatically deserialized from JSON) - key: The flag key - reason: Why the flag was enabled/disabled", + "details": "", + "category": null, + "params": [ + { + "name": "key", + "description": "The feature flag key.", + "isOptional": true, + "type": "str" + }, + { + "name": "distinct_id", + "description": "The user's distinct ID.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "Mapping of group type to group key.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "Person properties to use for evaluation.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "Group properties keyed by group type.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to evaluate only locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "send_feature_flag_events", + "description": "Whether to send a $feature_flag_called event.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup.", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "Optional device ID override for experience-continuity flags.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[FeatureFlagResult]" + } + }, + { + "id": "get_remote_config_payload", + "title": "get_remote_config_payload", + "description": "Get the payload for a remote config feature flag.", + "details": "", + "category": null, + "params": [ + { + "name": "key", + "description": "The key of the feature flag", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "get_tags", + "title": "get_tags", + "description": "Get all tags from the current context. Returns: Dict of all tags in the current context", + "details": "", + "category": "Contexts", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "dict[str, Any]" + } + }, + { + "id": "group_identify", + "title": "group_identify", + "description": "Set properties on a group.", + "details": "", + "category": "Identification", + "params": [ + { + "name": "group_type", + "description": "Type of your group. Required - the call is dropped with a warning if it is missing or empty.", + "isOptional": true, + "type": "str" + }, + { + "name": "group_key", + "description": "Unique identifier of the group. Required - the call is dropped with a warning if it is missing or empty.", + "isOptional": true, + "type": "str" + }, + { + "name": "properties", + "description": "Properties to set on the group", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "timestamp", + "description": "Optional timestamp for the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC.", + "isOptional": false, + "type": "datetime" + }, + { + "name": "uuid", + "description": "Optional UUID for the event", + "isOptional": true, + "type": "str" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup", + "isOptional": true, + "type": "bool" + }, + { + "name": "distinct_id", + "description": "Optional distinct ID of the user performing the action", + "isOptional": false, + "type": "Number" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Group identify", + "code": "# Group identify\nfrom posthog import group_identify\ngroup_identify('company', 'company_id_in_your_db', {\n 'name': 'Awesome Inc.',\n 'employees': 11\n})" + } + ] + }, + { + "id": "identify_context", + "title": "identify_context", + "description": "Identify the current context with a distinct ID.", + "details": "", + "category": "Identification", + "params": [ + { + "name": "distinct_id", + "description": "The distinct ID to associate with the current context and its children", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import identify_context\nidentify_context(\"user_123\")" + } + ] + }, + { + "id": "join", + "title": "join", + "description": "Attempt to process queued events and stop the client's background workers. Use `shutdown()` directly in most cases. Failed or undrainable events may be dropped and reported through logging or ``on_error``; returning does not guarantee server receipt. Lifecycle cleanup is attempted once, and cleanup failures are logged without retry.", + "details": "", + "category": "Client management", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import join\njoin()" + } + ] + }, + { + "id": "load_feature_flags", + "title": "load_feature_flags", + "description": "Load feature flag definitions from PostHog.", + "details": "", + "category": "Feature flags", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import load_feature_flags\nload_feature_flags()" + } + ] + }, + { + "id": "new_context", + "title": "new_context", + "description": "Create a new context scope that will be active for the duration of the with block.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "fresh", + "description": "Whether to start with a fresh context (default: False)", + "isOptional": false, + "type": "bool" + }, + { + "name": "capture_exceptions", + "description": "Whether to capture exceptions raised within the context. If omitted, defaults to the relevant client's exception autocapture setting.", + "isOptional": true, + "type": "bool" + }, + { + "name": "client", + "description": "Optional Posthog client instance to use for this context (default: None)", + "isOptional": true, + "type": "Client" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import new_context, tag, capture\nwith new_context():\n tag(\"request_id\", \"123\")\n capture(\"event_name\", properties={\"property\": \"value\"})" + } + ] + }, + { + "id": "scoped", + "title": "scoped", + "description": "Decorator that creates a new context for the function.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "fresh", + "description": "Whether to start with a fresh context (default: False)", + "isOptional": false, + "type": "bool" + }, + { + "name": "capture_exceptions", + "description": "Whether to capture and track exceptions with posthog error tracking. If omitted, defaults to the global exception autocapture setting.", + "isOptional": true, + "type": "bool" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import scoped, tag, capture\n@scoped()\ndef process_payment(payment_id):\n tag(\"payment_id\", payment_id)\n capture(\"payment_started\")" + } + ] + }, + { + "id": "set", + "title": "set", + "description": "Set properties on a user record.", + "details": "This will overwrite previous people property values. Generally operates similar to `capture`, with distinct_id being an optional argument, defaulting to the current context's distinct ID. If there is no context-level distinct ID, and no override distinct_id is passed, this function will do nothing. Context tags are folded into $set properties, so tagging the current context and then calling `set` will cause those tags to be set on the user (unlike capture, which causes them to just be set on the event).", + "category": "Identification", + "params": [ + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalSetArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Set person properties", + "code": "# Set person properties\nfrom posthog import set\nset(distinct_id='distinct_id', properties={'name': 'Max Hedgehog'})" + } + ] + }, + { + "id": "set_capture_exception_code_variables_context", + "title": "set_capture_exception_code_variables_context", + "description": "Override code-variable capture for exceptions in the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "enabled", + "description": "Whether exceptions captured in this context should include local variable values from stack frames.", + "isOptional": true, + "type": "bool" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "set_code_variables_detect_secrets_context", + "title": "set_code_variables_detect_secrets_context", + "description": "Whether to apply entropy-based secret detection as a last-resort redaction of high-entropy values (API keys, tokens, strong passwords) in captured code variables for the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "enabled", + "description": "", + "isOptional": true, + "type": "bool" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "set_code_variables_ignore_patterns_context", + "title": "set_code_variables_ignore_patterns_context", + "description": "Override code-variable ignore patterns for exceptions in the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "ignore_patterns", + "description": "Variable-name patterns that should be omitted entirely when code variables are captured.", + "isOptional": true, + "type": "list[str]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "set_code_variables_mask_patterns_context", + "title": "set_code_variables_mask_patterns_context", + "description": "Override code-variable mask patterns for exceptions in the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "mask_patterns", + "description": "Variable-name patterns whose values should be replaced with ``***`` when code variables are captured.", + "isOptional": true, + "type": "list[str]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "set_code_variables_mask_url_credentials_context", + "title": "set_code_variables_mask_url_credentials_context", + "description": "Whether to scrub credentials embedded in URLs/DSNs (e.g. user:pass@host) from captured code variables for the current context.", + "details": "", + "category": null, + "params": [ + { + "name": "enabled", + "description": "", + "isOptional": true, + "type": "bool" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "set_context_device_id", + "title": "set_context_device_id", + "description": "Set the device ID for the current context, associating all feature flag requests in this or child contexts with the given device ID.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "device_id", + "description": "The device ID to associate with the current context and its children", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import set_context_device_id\nset_context_device_id(\"device_123\")" + } + ] + }, + { + "id": "set_context_session", + "title": "set_context_session", + "description": "Set the session ID for the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "session_id", + "description": "The session ID to associate with the current context and its children", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import set_context_session\nset_context_session(\"session_123\")" + } + ] + }, + { + "id": "set_once", + "title": "set_once", + "description": "Set properties on a user record, only if they do not yet exist.", + "details": "This will not overwrite previous people property values, unlike `set`. Otherwise, operates in an identical manner to `set`.", + "category": "Identification", + "params": [ + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalSetArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Set property once", + "code": "# Set property once\nfrom posthog import set_once\nset_once(distinct_id='distinct_id', properties={'initial_url': '/blog'})" + } + ] + }, + { + "id": "setup", + "title": "setup", + "description": "Create or return the global PostHog client configured by module settings. Most applications should either instantiate ``Posthog`` directly or set ``posthog.api_key``/other module settings before calling top-level helpers. ``setup()`` is called automatically by global APIs such as ``capture()``. Returns: The global ``Client`` instance. If both ``api_key`` and ``project_api_key`` are missing or blank, the client is disabled and module-level calls become no-ops.", + "details": "", + "category": "Initialization", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Client" + } + }, + { + "id": "shutdown", + "title": "shutdown", + "description": "Attempt final delivery and permanently shut down the global client. Call this for final process cleanup, not at the end of each request or serverless invocation that shares the module-level client. Use ``flush()`` before returning from those invocations instead. Shutdown closes capture lanes, stops background workers and the feature flag poller, and tears down integrations. The global client is not recreated automatically after shutdown. This normally blocks until queued events have been attempted and cleanup finishes. Failed or undrainable events may be dropped and reported through logging or ``on_error``; returning does not guarantee server receipt. Event draining has no timeout. Queued spans get a final flush with up to 30 seconds waiting for an in-flight flush, then a fresh 30-second drain budget; the last HTTP request is not cut short. Unsent queued spans and still-open spans are discarded with a warning. Lifecycle cleanup is attempted once, and cleanup failures are logged without retry. Calls made directly from SDK callbacks such as ``on_error`` are deferred to avoid deadlocking the worker. If blocking completion is required, signal an application-owned thread, return from the callback, and call ``shutdown()`` from that thread. Do not wait inside a callback for another thread or task calling a lifecycle method.", + "details": "", + "category": "Client management", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import shutdown\nshutdown()" + } + ] + }, + { + "id": "start_span", + "title": "start_span", + "description": "Start a span for distributed tracing. Alpha. Returns a span handle. Use it as a context manager to make it the active span for the block and end it on exit (recording a raised exception on the way out); or call ``end()`` yourself for a span that cannot wrap a block. Spans started inside the block nest under it automatically. Always returns a usable handle, even when tracing is off, so calling code never branches.", + "details": "", + "category": "Tracing", + "params": [ + { + "name": "name", + "description": "A low-cardinality operation name, e.g. ``GET /users/:id``. Variable values belong in attributes, not the name.", + "isOptional": true, + "type": "str" + }, + { + "name": "kind", + "description": "``internal`` (default), ``server``, ``client``, ``producer`` or ``consumer``.", + "isOptional": true, + "type": "str" + }, + { + "name": "attributes", + "description": "Initial attributes.", + "isOptional": true, + "type": "Mapping[str, Any]" + }, + { + "name": "parent", + "description": "A span handle, or an inbound W3C ``traceparent`` header value to continue a remote trace. Defaults to the active span.", + "isOptional": false, + "type": "Span" + }, + { + "name": "tracestate", + "description": "The inbound ``tracestate`` header accompanying a ``traceparent`` string ``parent``; preserved and propagated.", + "isOptional": true, + "type": "str" + }, + { + "name": "start_time", + "description": "A ``datetime`` or epoch seconds, to backdate the span.", + "isOptional": false, + "type": "datetime" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Span" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "import posthog\nposthog.traces = {\"service_name\": \"checkout-api\"}\n\nwith posthog.start_span(\"POST /checkout\", parent=request.headers.get(\"traceparent\")) as span:\n span.set_attribute(\"plan\", user.plan)\n with posthog.start_span(\"db.query\", kind=\"client\"):\n ...\n outgoing_headers = {\"traceparent\": span.traceparent()}" + } + ] + }, + { + "id": "tag", + "title": "tag", + "description": "Add a tag to the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "name", + "description": "The tag key", + "isOptional": true, + "type": "str" + }, + { + "name": "value", + "description": "The tag value", + "isOptional": true, + "type": "Any" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import tag\ntag(\"user_id\", \"123\")" + } + ] + } + ] + } + ], + "categories": [ + "Initialization", + "Identification", + "Capture", + "Error Tracking", + "Feature flags", + "Tracing", + "Contexts", + "Events", + "Client management" + ] +} \ No newline at end of file diff --git a/references/posthog-python-references-latest.json b/references/posthog-python-references-latest.json index 66be86ee..a58eca35 100644 --- a/references/posthog-python-references-latest.json +++ b/references/posthog-python-references-latest.json @@ -2,7 +2,7 @@ "id": "posthog-python", "hogRef": "0.3", "info": { - "version": "7.78.0", + "version": "7.79.0", "id": "posthog-python", "title": "PostHog Python SDK", "description": "Integrate PostHog into any python application.", @@ -871,7 +871,7 @@ }, { "name": "only_evaluate_locally", - "description": "If True, never fall back to remote evaluation \u2014 flags that can't be evaluated locally are simply omitted from the snapshot.", + "description": "If True, never fall back to remote evaluation \u2014 flags that can't be evaluated locally are omitted from the snapshot and listed with a reason in its ``unresolved_flags``.", "isOptional": false, "type": "bool" }, @@ -2352,7 +2352,7 @@ }, { "name": "only_evaluate_locally", - "description": "If ``True``, never fall back to remote evaluation and omit flags that cannot be evaluated locally.", + "description": "If ``True``, never fall back to remote evaluation and omit flags that cannot be evaluated locally. The snapshot's ``unresolved_flags`` lists them with a reason.", "isOptional": false, "type": "bool" }, From 721355f527220f292f02a1c8a9e26137aad600f1 Mon Sep 17 00:00:00 2001 From: Daniel Visca Date: Thu, 29 Oct 2026 06:27:02 -0400 Subject: [PATCH 38/60] feat(metrics): metrics autocapture for HTTP, database and runtime metrics (#992) * feat(metrics): add metrics autocapture for HTTP, database and runtime metrics `metrics={"autocapture": True}` starts the installed OpenTelemetry instrumentors with private meter and tracer providers and exports to `/i/v1/metrics`. The packages come from the new `posthog[metrics]` extra. Co-Authored-By: Claude Opus 5.5 Generated-By: PostHog Desktop Task-Id: ef9b57c2-47ff-47e6-a509-8873ffe1fab4 * test: check concurrent LangChain traces without assuming order Generated-By: PostHog Desktop Task-Id: c0218209-575c-46f0-a201-d0c831e38096 * fix(metrics): address autocapture review findings - Honour send=False in the autocapture export gate. - Strip trailing slashes from the host before building the metrics endpoint. - Set OTEL_SEMCONV_STABILITY_OPT_IN only while the instrumentors load, then restore the process environment. - Limit each synchronous instrument to 2000 attribute sets; send the rest to an otel.metric.overflow series, so user-supplied hosts cannot grow the SDK cache. - After fork, replace the module lock and point the runtime instrumentor at the child process. Co-Authored-By: Claude Opus 5.5 Generated-By: PostHog Desktop Task-Id: 10838116-90e2-4d23-af38-8099d7887d07 * fix(metrics): keep app-owned instrumentors intact and hold ownership through shutdown - Skip an instrumentor whose singleton the app already instrumented before creating it. Creating it ran __init__ again and reset the app's system metrics config. - Uninstrument while the module lock is held, so a client that starts during shutdown cannot skip libraries that are about to be uninstrumented. The last export still runs outside the lock. - Correct the module docstring: an app cannot instrument a library again with its own OpenTelemetry setup while autocapture instruments it. Co-Authored-By: Claude Opus 5.5 Generated-By: PostHog Desktop Task-Id: a129c1a0-57e3-4370-a4df-6542e710ab75 * fix(metrics): use stable HTTP metric names when another instrumentor read the opt-in first OpenTelemetry reads OTEL_SEMCONV_STABILITY_OPT_IN once per process. If any instrumentor ran before the client started, autocapture sent http.client.duration (ms) and not http.client.request.duration (s). When the app has not set an opt-in, autocapture now sets the HTTP mode to stable after initialisation, so the names do not depend on import order. Co-Authored-By: Claude Opus 5.5 Generated-By: PostHog Desktop Task-Id: a129c1a0-57e3-4370-a4df-6542e710ab75 * fix(metrics): address the second review round of metrics autocapture - Do not send the app's OTEL_EXPORTER_OTLP_* settings (other vendors' headers, endpoint, TLS files) to PostHog: the private exporter is built without them. - Give HTTP instrumentors a no-op tracer, so app requests no longer carry an unsampled traceparent that makes downstream services drop their traces. - Record nothing while the client is disabled, and apply metrics.before_send to autocaptured measurements and runtime observations (value and attributes, or drop). - Exclude the PostHog host in any case and with or without its default port, escape commas, keep OTEL_PYTHON_*_EXCLUDED_URLS, and apply the exclusion to httpx. - Reset the reader's export lock in a forked child, export autocaptured metrics on Client.flush() and within the client's exit budget (no 10 s exit wait), roll back on a failed start, and uninstrument and reinstrument Flask apps across a restart. - Start autocapture on a later module-level setup(), once per client. - Validate flush_interval like client.metrics, use the same resource as client.metrics, keep the first word of db.operation.name, and raise the minimum OpenTelemetry versions to the first ones with the runtime metric names. - Rename the module to posthog/_metrics_autocapture.py to keep internals out of the public API. Co-Authored-By: Claude Opus 5.5 Generated-By: PostHog Desktop Task-Id: a129c1a0-57e3-4370-a4df-6542e710ab75 * fix(metrics): keep app middleware on shutdown, stop context propagation, and fix fork and flush issues - Flask: shutdown uninstruments an app only when its WSGI app is still the one the instrumentation set. uninstrument_app() would otherwise remove middleware the app added later, such as authentication. Instrumentation left in place records nothing after shutdown. - Do not propagate context: the instrumentors that autocapture starts no longer inject trace context or baggage into outgoing requests, including a trace extracted from an incoming request. - Keep the attributes that a database span starts with (asyncpg passes db.system then). - Export the last window at exit in sync mode. - Replace the metrics SDK collection and aggregation locks in a forked child, where a parent thread could hold them. - Use one flush thread at a time, with one follow-up export for calls made during a flush. - Run metrics.before_send as an SDK callback, so a flush() it calls is deferred and cannot deadlock the collection. - Raise the minimum OpenTelemetry instrumentation version to 0.60b0, the first in which the httpx instrumentor excludes URLs. Co-Authored-By: Claude Opus 5.5 Generated-By: PostHog Desktop Task-Id: a129c1a0-57e3-4370-a4df-6542e710ab75 * fix(metrics): do not let the Django instrumentor configure empty settings Without configured settings, DjangoInstrumentor calls settings.configure() with empty settings, so an app that configures its settings in code after creating the client fails with "Settings already configured". Autocapture now starts the Django instrumentor only when the settings are configured, as in AppConfig.ready(). Co-Authored-By: Claude Opus 5.5 Generated-By: PostHog Desktop Task-Id: a129c1a0-57e3-4370-a4df-6542e710ab75 * fix(metrics): drop raw URL attributes, normalize hook attributes, and stop flush loops from hooks - Never record raw URLs and paths (url.full, http.url, http.target, ...), client addresses, user agents or captured headers. Instrumentors such as Tornado put them on metrics, and they can hold tokens and emails. Routes stay. - Keep only valid OpenTelemetry attribute values, with sequences as tuples, before and after metrics.before_send. SDK 1.39 raised TypeError for a list value inside the app's request. Recording errors no longer reach the instrumented call. - A flush() called from a metrics before_send hook still flushes events and spans, but does not start another autocapture export, which ran the hook again without end. - Each flush request waits only for the first export that starts after it, not for later exports. Co-Authored-By: Claude Opus 5.5 Generated-By: PostHog Desktop Task-Id: a129c1a0-57e3-4370-a4df-6542e710ab75 * fix(metrics): isolate exporter settings and autocapture lifecycle state * fix(metrics): address remaining autocapture lifecycle feedback --------- Co-authored-by: Manoel Aranda Neto (cherry picked from commit febb3ed2f1584e68c05c73625becbdd18fe5f944) --- .sampo/changesets/metrics-autocapture.md | 13 + posthog/__init__.py | 11 +- posthog/_metrics_autocapture.py | 1312 ++++++++++++++ posthog/client.py | 158 +- posthog/metrics_capture.py | 45 +- posthog/test/ai/langchain/test_callbacks.py | 29 +- posthog/test/test_metrics_autocapture.py | 1716 +++++++++++++++++++ pyproject.toml | 21 + uv.lock | 206 ++- 9 files changed, 3474 insertions(+), 37 deletions(-) create mode 100644 .sampo/changesets/metrics-autocapture.md create mode 100644 posthog/_metrics_autocapture.py create mode 100644 posthog/test/test_metrics_autocapture.py diff --git a/.sampo/changesets/metrics-autocapture.md b/.sampo/changesets/metrics-autocapture.md new file mode 100644 index 00000000..d562f886 --- /dev/null +++ b/.sampo/changesets/metrics-autocapture.md @@ -0,0 +1,13 @@ +--- +pypi/posthog: minor +--- + +Add `metrics={"autocapture": True}` (alpha): HTTP, database and runtime metrics from the official OpenTelemetry instrumentations, with no instrumentation code. Install `posthog[metrics]` and run `opentelemetry-bootstrap -a install` to add the instrumentors for your libraries. Create the client before your web app (Django: in `AppConfig.ready()`): apps created earlier are not measured. For Flask, use module lookup as below, or import the `Flask` class only after the client starts; an earlier `from flask import Flask` alias is not instrumented. Already-served Flask apps are not re-instrumented after shutdown; create a new app for a replacement client. Autocapture does not start when the app already set up OpenTelemetry; export those metrics to PostHog instead. Raw URLs and paths, client addresses, user agents and captured headers are never recorded. `metrics.before_send` also applies to autocaptured measurements: it can change their value and attributes, or drop them. + +```python +import flask +from posthog import Posthog + +client = Posthog("phc_...", metrics={"autocapture": True}) +app = flask.Flask(__name__) +``` diff --git a/posthog/__init__.py b/posthog/__init__.py index 312e43f8..0fb35474 100644 --- a/posthog/__init__.py +++ b/posthog/__init__.py @@ -401,7 +401,14 @@ def get_context_options() -> Dict[str, Any]: metrics: Config dict for the ``client.metrics`` API (``service_name``, ``service_version``, ``environment``, ``flush_interval``, ...). Applied when ``setup()`` builds the global client, or on a later ``setup()`` - call if the metrics API hasn't been used yet. + call if the metrics API hasn't been used yet. ``autocapture`` (alpha, + ``True`` or ``{"http": ..., "db": ..., "runtime": ...}``) starts HTTP, + database and runtime metrics from the OpenTelemetry instrumentations + when ``setup()`` runs with it; call ``setup()`` before the web app is + created, because apps created earlier are not measured. For Flask, + use ``flask.Flask(...)`` after setup, or import the ``Flask`` class only + after setup. Already-served Flask apps are not re-instrumented after + shutdown; create a new app for a replacement client. traces: Config dict for distributed tracing: ``service_name``, ``service_version``, ``environment``, ``resource_attributes``, ``flush_interval`` (5 s), ``max_queue_size`` (2048), @@ -1554,6 +1561,8 @@ def setup() -> Client: # already forced setup()) still applies until the metrics API is first used. if default_client._metrics is None: default_client._metrics_config = metrics + # Autocapture is not lazy, so a late `metrics` option starts it here. + default_client._start_metrics_autocapture_once(metrics) # traces=None means off, so the module option applies only where the client # has none; False is the latch of an init that failed and stays off. if traces is not None and default_client._traces_config is None: diff --git a/posthog/_metrics_autocapture.py b/posthog/_metrics_autocapture.py new file mode 100644 index 00000000..4771f2d5 --- /dev/null +++ b/posthog/_metrics_autocapture.py @@ -0,0 +1,1312 @@ +"""Metrics autocapture (``metrics={"autocapture": True}``) — alpha. + +HTTP, database and runtime metrics from the official OpenTelemetry +instrumentations, exported to PostHog's ``/i/v1/metrics`` with no +instrumentation code in the app. + +The OpenTelemetry packages come from the ``posthog[metrics]`` extra and are +imported only when autocapture starts. Library instrumentors (requests, Django, +psycopg, ...) are found through the ``opentelemetry_instrumentor`` entry points, +the same way ``opentelemetry-instrument`` finds them. An instrumentor changes the +library classes when it starts, so objects created before that, such as a Flask +or FastAPI app, are not measured: create the client before the web app. For Flask, +use ``import flask`` and construct ``flask.Flask(...)`` after starting the client, +or import the ``Flask`` class only afterward. An already-served Flask app is not +instrumented again after shutdown; create a new app for a replacement client. +Django is instrumented only when its settings are configured, as in ``AppConfig.ready()``. + +The meter and tracer providers are private: nothing is set on the OpenTelemetry +globals, and the app's ``OTEL_EXPORTER_OTLP_*`` settings are not used. The +library instrumentors are process-wide singletons, though, so an app cannot +instrument a library again with its own OpenTelemetry setup while autocapture +instruments it. Apps with their own OpenTelemetry instrumentation should export +their metrics to PostHog and not turn on autocapture. + +Raw URLs and paths, client addresses, user agents and captured headers are never +recorded; routes (such as ``/users/{id}``) are. Spans are only used to derive +database metrics and are never exported. The HTTP +instrumentors get no tracer, and the instrumentors that autocapture starts do not +propagate context, so no trace context or baggage goes to other services. +""" + +import importlib +import logging +import os +import re +import sys +import threading +import weakref +from contextlib import contextmanager +from importlib.metadata import entry_points +from typing import Any, Callable, Dict, Iterator, List, Optional, Tuple +from urllib.parse import urlsplit + +from .version import VERSION + +try: + from opentelemetry.sdk.trace import SpanProcessor as _SpanProcessorBase +except ImportError: # pragma: no cover - the processor is only built once OTel imports + _SpanProcessorBase = object + +log = logging.getLogger("posthog") + +_import_module = importlib.import_module + +_INSTALL_HINT = 'pip install "posthog[metrics]" && opentelemetry-bootstrap -a install' + +_HTTP_CLIENT_INSTRUMENTORS = ( + "requests", + "urllib", + "urllib3", + "httpx", + "aiohttp-client", +) +_HTTP_SERVER_INSTRUMENTORS = ( + "django", + "flask", + "fastapi", + "starlette", + "falcon", + "pyramid", + "tornado", + "aiohttp-server", +) +_DB_DRIVER_INSTRUMENTORS = ( + "psycopg2", + "psycopg", + "asyncpg", + "pymysql", + "mysql", + "mysqlclient", + "sqlite3", + "pymongo", + "redis", + "pymssql", + "cassandra", +) +# SQLAlchemy adds connection pool metrics. Its spans wrap a driver span, so they +# are not turned into operation metrics: that would count one query twice. +_DB_INSTRUMENTORS = _DB_DRIVER_INSTRUMENTORS + ("sqlalchemy",) +_DERIVED_DB_METRIC_SCOPES = frozenset( + f"opentelemetry.instrumentation.{name}" for name in _DB_DRIVER_INSTRUMENTORS +) + +# Process-level metrics only; the host-wide system.* metrics are left out. +_RUNTIME_CONFIG: Dict[str, Optional[List[str]]] = { + "process.cpu.time": ["user", "system"], + "process.cpu.utilization": ["user", "system"], + "process.memory.usage": None, + "process.memory.virtual": None, + "process.open_file_descriptor.count": None, + "process.thread.count": None, + "process.context_switches": ["involuntary", "voluntary"], + "cpython.gc.collections": None, + "cpython.gc.collected_objects": None, + "cpython.gc.uncollectable_objects": None, +} + +_DB_OPERATION_DURATION = "db.client.operation.duration" +# OpenTelemetry semantic convention advice for `db.client.operation.duration`. +_DB_DURATION_BUCKETS = [0.001, 0.005, 0.01, 0.05, 0.1, 0.5, 1, 5, 10] +# The leading keyword (`SELECT`, `GET`) is bounded; the rest of a statement is not. +_STATEMENT_KEYWORD = re.compile(r"^\s*([A-Za-z]{2,20})\b") + +# The OpenTelemetry SDK keeps every attribute set it has seen, also with delta +# temporality, and has no limit of its own. Some attributes, such as the +# `server.address` of an outgoing request, can come from user input. So each +# instrument keeps at most this many attribute sets. Other measurements go to one +# overflow series, as the OpenTelemetry cardinality limit specifies. +_MAX_ATTRIBUTE_SETS = 2000 +_OVERFLOW_ATTRIBUTES = {"otel.metric.overflow": True} + +# Attributes that some instrumentors put on metrics, such as Tornado's raw +# `http.target` and `http.url`. Paths and URLs can hold tokens and emails, and +# client addresses and user agents identify users. They are never recorded. +_UNSAFE_ATTRIBUTES = frozenset( + { + "http.url", + "http.target", + "url.full", + "url.path", + "url.query", + "http.client_ip", + "client.address", + "net.peer.ip", + "net.sock.peer.addr", + "http.user_agent", + "user_agent.original", + } +) +_UNSAFE_ATTRIBUTE_PREFIXES = ("http.request.header.", "http.response.header.") +_ATTRIBUTE_VALUE_TYPES = (str, bool, int, float) + +_SEMCONV_OPT_IN = "OTEL_SEMCONV_STABILITY_OPT_IN" +_EXPORT_TIMEOUT_SECONDS = 10.0 +_DEFAULT_PORTS = {"http": 80, "https": 443} + +# The `metrics.before_send` sample type of each instrument kind. +_SAMPLE_TYPES = { + "create_counter": "count", + "create_up_down_counter": "count", + "create_histogram": "histogram", + "create_gauge": "gauge", + "create_observable_counter": "count", + "create_observable_up_down_counter": "count", + "create_observable_gauge": "gauge", +} + +_lock = threading.Lock() +_active: Optional["MetricsAutocapture"] = None +_warned_about_missing_packages = False +# Flask apps created while autocapture runs. FlaskInstrumentor.uninstrument() +# restores only the Flask class, so shutdown uninstruments these apps. A later +# autocapture can instrument them again only before their first request. +_flask_apps: "weakref.WeakSet[Any]" = weakref.WeakSet() +# The `inject` functions that autocapture replaced in instrumentor modules, +# by module name. +_replaced_injects: Dict[str, Any] = {} +_replaced_aiohttp_trace_config: Optional[Tuple[Callable, Callable]] = None + + +def _always_enabled() -> bool: + return True + + +def _apply_before_send( + before_send: Callable, + name: str, + metric_type: str, + unit: str, + value: Any, + attributes: Any, +) -> Optional[Tuple[Any, Any]]: + """Runs the ``metrics.before_send`` hook on one measurement, like the + ``client.metrics`` API does. Returns the value and attributes to record, or + ``None`` to drop it. An instrument's name, type and unit are fixed, so the + hook can change only the value and the attributes.""" + sample = { + "name": name, + "type": metric_type, + "value": value, + "unit": unit, + "attributes": dict(attributes) if attributes else {}, + } + try: + filtered = before_send(sample) + except Exception as e: + log.error("Error in metrics before_send: %s", e) + return None + if not filtered: + return None + if not isinstance(filtered, dict): + log.warning( + "Dropping metric: before_send must return the sample dict or a falsy value" + ) + return None + value = filtered.get("value") + attributes = filtered.get("attributes") + if ( + not isinstance(value, (int, float)) + or isinstance(value, bool) + or not isinstance(attributes, (dict, type(None))) + ): + log.warning( + "Dropping metric '%s': before_send returned an invalid value or attributes", + name, + ) + return None + return value, attributes + + +def _clean_attributes(attributes: Any) -> Any: + """Removes the unsafe attributes, and keeps only valid OpenTelemetry values, + with sequences as tuples. Some OpenTelemetry versions raise for a list value, + and `before_send` can return any value.""" + if not attributes: + return attributes + try: + items = list(attributes.items()) + except (AttributeError, TypeError): + return None + cleaned: Dict[str, Any] = {} + for key, value in items: + if ( + not isinstance(key, str) + or key in _UNSAFE_ATTRIBUTES + or key.startswith(_UNSAFE_ATTRIBUTE_PREFIXES) + ): + continue + if isinstance(value, _ATTRIBUTE_VALUE_TYPES): + cleaned[key] = value + elif isinstance(value, (list, tuple)) and all( + isinstance(item, _ATTRIBUTE_VALUE_TYPES) for item in value + ): + cleaned[key] = tuple(value) + return cleaned + + +def _instrument_name_and_unit(args: tuple, kwargs: dict, unit_index: int): + name = kwargs.get("name", args[0] if args else "") + unit = kwargs.get("unit", args[unit_index] if len(args) > unit_index else "") + return name, unit + + +class _BoundedInstrument: + """A synchronous instrument that records only while autocapture is enabled, + applies ``before_send``, and sends new attribute sets to the overflow series + once it has seen ``limit`` of them.""" + + def __init__( + self, + instrument: Any, + limit: int, + is_enabled: Callable[[], bool] = _always_enabled, + before_send: Optional[Callable] = None, + name: str = "", + metric_type: str = "", + unit: str = "", + stopped: Optional[threading.Event] = None, + ): + self._instrument = instrument + self._stopped = stopped + self._limit = limit + self._is_enabled = is_enabled + self._before_send = before_send + self._name = name + self._metric_type = metric_type + self._unit = unit + # No lock: a race can only go over the limit by the number of threads. + self._seen: set = set() + + def _bounded(self, attributes: Any) -> Any: + try: + key = frozenset(attributes.items()) if attributes else frozenset() + except (AttributeError, TypeError): + return attributes + if key in self._seen: + return attributes + if len(self._seen) < self._limit: + self._seen.add(key) + return attributes + return _OVERFLOW_ATTRIBUTES + + def _record(self, method: str, amount, attributes, args, kwargs): + # Like `client.metrics`, nothing is recorded while the client is disabled, + # so that period is not exported once the client is enabled again. + # Instrumentation that shutdown leaves in place records nothing more. + if ( + self._stopped is not None and self._stopped.is_set() + ) or not self._is_enabled(): + return None + attributes = _clean_attributes(attributes) + if self._before_send is not None: + measurement = _apply_before_send( + self._before_send, + self._name, + self._metric_type, + self._unit, + amount, + attributes, + ) + if measurement is None: + return None + amount, attributes = measurement + attributes = _clean_attributes(attributes) + # This runs in the instrumented call, such as the app's HTTP request. + try: + return getattr(self._instrument, method)( + amount, self._bounded(attributes), *args, **kwargs + ) + except Exception: + log.debug( + "Metrics autocapture failed to record %s", self._name, exc_info=True + ) + return None + + def add(self, amount, attributes=None, *args, **kwargs): + return self._record("add", amount, attributes, args, kwargs) + + def record(self, amount, attributes=None, *args, **kwargs): + return self._record("record", amount, attributes, args, kwargs) + + def set(self, amount, attributes=None, *args, **kwargs): + return self._record("set", amount, attributes, args, kwargs) + + def __getattr__(self, name): + return getattr(self._instrument, name) + + +class _BoundedMeter: + def __init__( + self, + meter: Any, + limit: int, + instruments: Dict[Any, _BoundedInstrument], + is_enabled: Callable[[], bool] = _always_enabled, + before_send: Optional[Callable] = None, + stopped: Optional[threading.Event] = None, + ): + self._meter = meter + self._limit = limit + self._instruments = instruments + self._is_enabled = is_enabled + self._before_send = before_send + self._stopped = stopped + + def _bounded(self, factory: str, args, kwargs) -> _BoundedInstrument: + name, unit = _instrument_name_and_unit(args, kwargs, unit_index=1) + instrument = getattr(self._meter, factory)(*args, **kwargs) + if instrument in self._instruments: + return self._instruments[instrument] + return self._instruments.setdefault( + instrument, + _BoundedInstrument( + instrument, + self._limit, + self._is_enabled, + self._before_send, + name, + _SAMPLE_TYPES[factory], + unit, + self._stopped, + ), + ) + + def _observable(self, factory: str, args, kwargs): + # Observable instruments report from bounded callbacks (runtime metrics), + # so only the enabled check and `before_send` apply to them. They still + # report in the last collection of shutdown. + name, unit = _instrument_name_and_unit(args, kwargs, unit_index=2) + args = list(args) + if len(args) > 1: + args[1] = self._gated_callbacks(args[1], name, _SAMPLE_TYPES[factory], unit) + elif "callbacks" in kwargs: + kwargs["callbacks"] = self._gated_callbacks( + kwargs["callbacks"], name, _SAMPLE_TYPES[factory], unit + ) + return getattr(self._meter, factory)(*args, **kwargs) + + def _gated_callbacks(self, callbacks, name: str, metric_type: str, unit: str): + if not callbacks: + return callbacks + is_enabled, before_send = self._is_enabled, self._before_send + observation_class = _import_module("opentelemetry.metrics").Observation + + def gated(callback): + def observe(options): + if not is_enabled(): + return [] + observations = callback(options) + if before_send is None: + return observations + kept = [] + for observation in observations or (): + measurement = _apply_before_send( + before_send, + name, + metric_type, + unit, + observation.value, + observation.attributes, + ) + if measurement is not None: + value, attributes = measurement + kept.append( + observation_class(value, _clean_attributes(attributes)) + ) + return kept + + return observe + + # Generator objects are also valid callbacks; they are passed on as they are. + return [ + gated(callback) if callable(callback) else callback + for callback in callbacks + ] + + def create_counter(self, *args, **kwargs): + return self._bounded("create_counter", args, kwargs) + + def create_up_down_counter(self, *args, **kwargs): + return self._bounded("create_up_down_counter", args, kwargs) + + def create_histogram(self, *args, **kwargs): + return self._bounded("create_histogram", args, kwargs) + + def create_gauge(self, *args, **kwargs): + return self._bounded("create_gauge", args, kwargs) + + def create_observable_counter(self, *args, **kwargs): + return self._observable("create_observable_counter", args, kwargs) + + def create_observable_up_down_counter(self, *args, **kwargs): + return self._observable("create_observable_up_down_counter", args, kwargs) + + def create_observable_gauge(self, *args, **kwargs): + return self._observable("create_observable_gauge", args, kwargs) + + def __getattr__(self, name): + return getattr(self._meter, name) + + +class _BoundedMeterProvider: + """The meter provider given to the instrumentors. Its instruments record only + while autocapture is enabled, apply ``before_send``, and limit the number of + attribute sets of each synchronous instrument.""" + + def __init__( + self, + meter_provider: Any, + limit: int, + is_enabled: Callable[[], bool] = _always_enabled, + before_send: Optional[Callable] = None, + stopped: Optional[threading.Event] = None, + ): + self._meter_provider = meter_provider + self._instruments: Dict[Any, _BoundedInstrument] = {} + self._limit = limit + self._is_enabled = is_enabled + self._before_send = before_send + self._stopped = stopped + + def get_meter(self, *args, **kwargs): + return _BoundedMeter( + self._meter_provider.get_meter(*args, **kwargs), + self._limit, + self._instruments, + self._is_enabled, + self._before_send, + self._stopped, + ) + + def __getattr__(self, name): + return getattr(self._meter_provider, name) + + +@contextmanager +def _stable_http_semconv() -> Iterator[None]: + """Selects the stable HTTP names (`http.server.request.duration`, seconds), + the same names posthog-node sends, while the instrumentors load. + + OpenTelemetry reads the opt-in once per process. If another instrumentor + read it first, without an opt-in, the HTTP mode is set here, so the names + do not depend on import order. The process environment is restored + afterwards, so subprocesses and other code do not see the change. + An opt-in that the app set is kept.""" + previous = os.environ.get(_SEMCONV_OPT_IN) + if previous is None: + os.environ[_SEMCONV_OPT_IN] = "http" + try: + try: + semconv = _import_module("opentelemetry.instrumentation._semconv") + stability = semconv._OpenTelemetrySemanticConventionStability + stability._initialize() + if previous is None: + stability._OTEL_SEMCONV_STABILITY_SIGNAL_MAPPING[ + semconv._OpenTelemetryStabilitySignalType.HTTP + ] = semconv._StabilityMode.HTTP + except Exception: # older instrumentation packages have no opt-in + pass + yield + finally: + if previous is None: + os.environ.pop(_SEMCONV_OPT_IN, None) + + +@contextmanager +def _patched_environ(values: Dict[str, Optional[str]]) -> Iterator[None]: + """Sets the environment variables (``None`` removes one) while OpenTelemetry + reads them, then restores the process environment.""" + previous = {key: os.environ.get(key) for key in values} + try: + for key, value in values.items(): + if value is None: + os.environ.pop(key, None) + else: + os.environ[key] = value + yield + finally: + for key, value in previous.items(): + if value is None: + os.environ.pop(key, None) + else: + os.environ[key] = value + + +def _posthog_url_pattern(host: str) -> str: + """An ``excluded_urls`` pattern for the PostHog host, in every form the HTTP + instrumentors see it: in any case, and with or without the default port.""" + parts = urlsplit(host) + scheme = parts.scheme.lower() + hostname = parts.hostname + if not scheme or not hostname: + pattern = "^" + re.escape(host) + "(?:[/?#]|$)" + else: + if ":" in hostname: + hostname = f"[{hostname}]" + default_port = _DEFAULT_PORTS.get(scheme) + if parts.port is not None and parts.port != default_port: + port = f":{parts.port}" + else: + port = f"(?::{default_port})?" if default_port else "" + pattern = ( + "^(?i:" + + re.escape(f"{scheme}://{hostname}") + + ")" + + port + + re.escape(parts.path.rstrip("/")) + + "(?:[/?#]|$)" + ) + # The instrumentors split the excluded URLs at commas. + return pattern.replace(",", r"\x2c") + + +def _excluded_urls(instrumentor_name: str, posthog_pattern: str) -> Tuple[str, str]: + """The excluded URLs for an HTTP client instrumentor: the PostHog host, and + the URLs that the app excludes in ``OTEL_PYTHON_*_EXCLUDED_URLS``. Returns + the instrumentor's environment variable too, which httpx and aiohttp read + in place of the ``excluded_urls`` argument.""" + variable = ( + f"OTEL_PYTHON_{instrumentor_name.upper().replace('-', '_')}_EXCLUDED_URLS" + ) + excluded = os.environ.get(variable, os.environ.get("OTEL_PYTHON_EXCLUDED_URLS", "")) + return variable, ",".join(url for url in (posthog_pattern, excluded) if url) + + +def _track_flask_apps(instrumentor_class: Any, providers: Dict[str, Any]) -> None: + flask: Any = _import_module("flask") + instrumented_flask = flask.Flask + + class _TrackedFlask(instrumented_flask): # type: ignore[misc,valid-type] + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + # FlaskInstrumentor.uninstrument_app() needs `_teardown_request`, which + # only instrument_app() sets. The instrumented class registers that + # hook last. + teardown_funcs = self.teardown_request_funcs.get(None) + if not hasattr(self, "_teardown_request") and teardown_funcs: + self._teardown_request = teardown_funcs[-1] + self._posthog_wsgi_app = self.wsgi_app + _flask_apps.add(self) + + flask.Flask = _TrackedFlask + for app in list(_flask_apps): + if not getattr(app, "_is_instrumented_by_opentelemetry", False): + if app._got_first_request: + log.warning( + "Metrics autocapture skipped Flask app %r: it has already handled a request", + app, + ) + continue + instrumentor_class.instrument_app(app, **providers) + app._posthog_wsgi_app = app.wsgi_app + + +def _uninstrument_flask_apps() -> None: + flask_instrumentation = sys.modules.get("opentelemetry.instrumentation.flask") + if flask_instrumentation is None: + return + for app in list(_flask_apps): + if not getattr(app, "_is_instrumented_by_opentelemetry", False): + continue + # uninstrument_app() restores the WSGI app saved when the app was + # instrumented, which would remove middleware that the app added later, + # such as authentication. Such an app keeps the instrumentation, which + # records nothing after shutdown, and is not measured again. + if getattr(app, "wsgi_app", None) is not getattr( + app, "_posthog_wsgi_app", None + ): + log.debug( + "Metrics autocapture left the instrumentation of %r in place", app + ) + continue + flask_instrumentation.FlaskInstrumentor.uninstrument_app(app) + + +def _django_settings_configured() -> bool: + conf = sys.modules.get("django.conf") + return bool(getattr(getattr(conf, "settings", None), "configured", False)) + + +def _no_inject(*args: Any, **kwargs: Any) -> None: + return None + + +def _disable_propagation(instrumentor_class: Any) -> None: + """Replaces ``inject`` in the instrumentor's modules, which would send the + current trace context and baggage to other services. The instrumentors call + it through their module global, and autocapture owns these instrumentors + while it runs.""" + inject = _import_module("opentelemetry.propagate").inject + package = instrumentor_class.__module__ + if package.startswith("opentelemetry.instrumentation."): + # Such as `opentelemetry.instrumentation.tornado`, with its client module. + package = ".".join(package.split(".")[:3]) + for name, module in list(sys.modules.items()): + if name != package and not name.startswith(package + "."): + continue + if getattr(module, "inject", None) is inject: + _replaced_injects[name] = inject + setattr(module, "inject", _no_inject) + + +def _gate_aiohttp_trace_configs( + meter_provider: Any, stopped: threading.Event, posthog_url_pattern: str +) -> None: + global _replaced_aiohttp_trace_config + module = _import_module("opentelemetry.instrumentation.aiohttp_client") + original = module.create_trace_config + posthog_urls = re.compile(posthog_url_pattern) + + def create_trace_config(*args, **kwargs): + config = original(*args, **kwargs) + if kwargs.get("meter_provider") is meter_provider: + on_start = config.on_request_start[0] + + async def on_request_start(session, trace_config_ctx, params): + if stopped.is_set() or posthog_urls.search(str(params.url)): + trace_config_ctx.span = None + return + await on_start(session, trace_config_ctx, params) + + # Existing sessions retain these callbacks after uninstrumentation. + # Keep them inert before the module's real inject function is restored. + config.on_request_start[0] = on_request_start + return config + + setattr(module, "create_trace_config", create_trace_config) + _replaced_aiohttp_trace_config = original, create_trace_config + + +def _restore_propagation() -> None: + while _replaced_injects: + name, inject = _replaced_injects.popitem() + module = sys.modules.get(name) + if module is not None and getattr(module, "inject", None) is _no_inject: + setattr(module, "inject", inject) + + +def _first_string(attributes: Any, keys: tuple) -> Optional[str]: + for key in keys: + value = attributes.get(key) + if isinstance(value, str) and value: + return value + return None + + +class DbSpanMetricsProcessor(_SpanProcessorBase): + """Turns driver-level database client spans into a + ``db.client.operation.duration`` histogram. + + Only bounded attributes are kept: never the statement, which holds values, + and never anything per-user. + """ + + def __init__(self, meter): + try: + self._histogram = meter.create_histogram( + _DB_OPERATION_DURATION, + unit="s", + description="Duration of database client operations.", + explicit_bucket_boundaries_advisory=_DB_DURATION_BUCKETS, + ) + except TypeError: + # opentelemetry-api before 1.23 has no bucket advice. + self._histogram = meter.create_histogram( + _DB_OPERATION_DURATION, + unit="s", + description="Duration of database client operations.", + ) + + def on_start(self, span, parent_context=None): + pass + + def on_end(self, span): + try: + self._record(span) + except Exception: # never raise into the instrumented call + log.debug( + "Metrics autocapture failed to record a database span", exc_info=True + ) + + def _record(self, span): + from opentelemetry.trace import SpanKind, StatusCode + + scope = getattr(span, "instrumentation_scope", None) + if ( + span.kind != SpanKind.CLIENT + or getattr(scope, "name", None) not in _DERIVED_DB_METRIC_SCOPES + ): + return + attributes = span.attributes or {} + system = _first_string(attributes, ("db.system.name", "db.system")) + if not system or span.start_time is None or span.end_time is None: + return + metric_attributes: Dict[str, Any] = {"db.system.name": system} + operation = _first_string(attributes, ("db.operation.name", "db.operation")) + if operation is None: + statement = _first_string(attributes, ("db.query.text", "db.statement")) + match = _STATEMENT_KEYWORD.match(statement or "") + operation = match.group(1).upper() if match else None + else: + # Only the leading keyword is bounded, as for a statement. + words = operation.split() + operation = words[0] if words else None + collection = _first_string( + attributes, + ( + "db.collection.name", + "db.mongodb.collection", + "db.sql.table", + "db.cassandra.table", + ), + ) + namespace = _first_string(attributes, ("db.namespace", "db.name")) + address = _first_string(attributes, ("server.address", "net.peer.name")) + port = attributes.get("server.port", attributes.get("net.peer.port")) + if operation: + metric_attributes["db.operation.name"] = operation + if collection: + metric_attributes["db.collection.name"] = collection + if namespace: + metric_attributes["db.namespace"] = namespace + if address: + metric_attributes["server.address"] = address + if isinstance(port, int) and not isinstance(port, bool): + metric_attributes["server.port"] = port + if span.status.status_code == StatusCode.ERROR: + metric_attributes["error.type"] = ( + _first_string(attributes, ("error.type",)) or "_OTHER" + ) + self._histogram.record( + (span.end_time - span.start_time) / 1e9, metric_attributes + ) + + def _on_ending(self, span): + pass + + def shutdown(self): + pass + + def force_flush(self, timeout_millis=30000): + return True + + +class MetricsAutocapture: + """The running autocapture. ``shutdown()`` exports the last window and + removes the instrumentation.""" + + def __init__( + self, + meter_provider, + tracer_provider, + instrumentors, + reader, + stopped: Optional[threading.Event] = None, + ): + self._meter_provider = meter_provider + self._tracer_provider = tracer_provider + self._instrumentors = instrumentors + self._reader = reader + self._stopped = stopped if stopped is not None else threading.Event() + self._reset_flush_state() + + def force_flush(self, timeout_millis: int = 10_000) -> None: + self._meter_provider.force_flush(timeout_millis) + + def _reset_flush_state(self) -> None: + self._flush_done = threading.Condition() + self._flush_thread: Optional[threading.Thread] = None + # Flush requests are numbered. An export covers the requests made + # before it starts. + self._flush_requested = 0 + self._flush_exported = 0 + + def start_flush(self) -> Optional[Callable[[Optional[float]], bool]]: + """Starts an export of the current window. Returns a function that waits + for it, at most the given seconds, and returns whether it finished; or + ``None`` when there is nothing to wait for. + + One thread exports at a time. A call made while it exports gets one more + export, so the measurements recorded before the call are also sent, and + callers with short timeouts do not start a thread each. Each call waits + only for the first export that starts after it.""" + with self._flush_done: + self._flush_requested += 1 + request = self._flush_requested + flusher = self._flush_thread + if flusher is threading.current_thread(): + # From the export itself, waiting would never end. + return None + if flusher is None: + flusher = threading.Thread( + target=self._flush_until_done, + name="posthog-metrics-autocapture-flush", + daemon=True, + ) + try: + flusher.start() + except RuntimeError: # no new threads at interpreter shutdown + self._flush_requested -= 1 + return None + self._flush_thread = flusher + + def wait(timeout: Optional[float] = None) -> bool: + with self._flush_done: + return self._flush_done.wait_for( + lambda: self._flush_exported >= request, timeout + ) + + return wait + + def _flush_until_done(self) -> None: + while True: + with self._flush_done: + covered = self._flush_requested + self._flush_quietly() + with self._flush_done: + self._flush_exported = covered + self._flush_done.notify_all() + if self._flush_requested == covered: + self._flush_thread = None + return + + def _flush_quietly(self) -> None: + try: + self._meter_provider.force_flush() + except Exception: + log.debug("Metrics autocapture failed to flush", exc_info=True) + + def _reinit_after_fork(self) -> None: + # The instrumentation and the providers stay active in the child, and the + # OpenTelemetry metric reader restarts its export thread there. The reader + # does not replace its export lock, which the parent's export thread can + # hold at fork time. + if getattr(self._reader, "_export_lock", None) is not None: + self._reader._export_lock = threading.Lock() + # The parent's flush thread does not exist in the child. + self._reset_flush_state() + try: + _replace_collection_locks(self._meter_provider) + except Exception: + log.debug( + "Metrics autocapture failed to reset locks after fork", exc_info=True + ) + # The runtime instrumentor keeps the process it was created in, so it + # would report the parent process. + for instrumentor in self._instrumentors: + try: + psutil = _import_module("psutil") + if isinstance(getattr(instrumentor, "_proc", None), psutil.Process): + instrumentor._proc = psutil.Process(os.getpid()) + except Exception: + log.debug( + "Metrics autocapture failed to update %s after fork", + instrumentor, + exc_info=True, + ) + + def shutdown(self) -> None: + global _active + with _lock: + if _active is not self: + return + self._stopped.set() + # Uninstrument before another client can start. It would skip the + # libraries that are still instrumented, and then lose them here. + _uninstrument(self._instrumentors) + _active = None + # The last export can take a while, so it runs outside the lock. + _shut_down(self._tracer_provider, self._meter_provider) + + +def _uninstrument(instrumentors: List[Any]) -> None: + global _replaced_aiohttp_trace_config + for instrumentor in instrumentors: + try: + instrumentor.uninstrument() + except Exception: + log.debug( + "Metrics autocapture failed to uninstrument %s", + instrumentor, + exc_info=True, + ) + try: + _uninstrument_flask_apps() + except Exception: + log.debug( + "Metrics autocapture failed to uninstrument Flask apps", exc_info=True + ) + if _replaced_aiohttp_trace_config is not None: + original, replacement = _replaced_aiohttp_trace_config + module = sys.modules.get("opentelemetry.instrumentation.aiohttp_client") + if module is not None and module.create_trace_config is replacement: + setattr(module, "create_trace_config", original) + _replaced_aiohttp_trace_config = None + _restore_propagation() + + +def _fresh_lock(owner: Any, name: str = "_lock") -> None: + lock = getattr(owner, name, None) + if lock is None: + return + rlock_type = type(threading.RLock()) + setattr( + owner, + name, + threading.RLock() if isinstance(lock, rlock_type) else threading.Lock(), + ) + + +def _replace_collection_locks(meter_provider: Any) -> None: + """Replaces the locks of the metrics SDK that a parent thread can hold at + fork time, while it collects or records. The SDK replaces only the locks of + the meter provider itself. In the child, no thread holds these locks.""" + for meter in list(getattr(meter_provider, "_meters", {}).values()): + _fresh_lock(meter, "_instrument_registration_lock") + consumer = getattr(meter_provider, "_measurement_consumer", None) + if consumer is None: + return + _fresh_lock(consumer) + for storage in list(getattr(consumer, "_reader_storages", {}).values()): + _fresh_lock(storage) + matches_by_instrument = getattr( + storage, "_instrument_view_instrument_matches", {} + ) + for matches in list(matches_by_instrument.values()): + for match in matches: + _fresh_lock(match) + for aggregation in list( + getattr(match, "_attributes_aggregation", {}).values() + ): + _fresh_lock(aggregation) + _fresh_lock(getattr(aggregation, "_reservoir", None)) + + +def _shut_down(*components: Any) -> None: + for component in components: + if component is None: + continue + try: + component.shutdown() + except Exception: + log.debug( + "Metrics autocapture failed to shut down %s", + component, + exc_info=True, + ) + + +def _reinit_after_fork() -> None: + global _lock + # A parent thread can hold the lock at fork time. Replace it, never acquire it. + _lock = threading.Lock() + active = _active + if active is not None: + active._reinit_after_fork() + + +if hasattr(os, "register_at_fork"): + os.register_at_fork(after_in_child=_reinit_after_fork) + + +def _metrics_export_session(api_key: str) -> Any: + session_base: Any = _import_module("requests").Session + + class _MetricsSession(session_base): + def request(self, method, url, **kwargs): + # Exporters merge application OTLP headers and TLS settings even with + # explicit constructor arguments. Isolate them at the private transport. + self.headers.clear() + self.cert = None + kwargs.update( + headers={ + "Authorization": f"Bearer {api_key}", + "Content-Type": "application/x-protobuf", + }, + verify=True, + cert=None, + ) + return super().request(method, url, **kwargs) + + return _MetricsSession() + + +def _is_default_provider(provider: Any) -> bool: + name = type(provider).__name__ + return name.startswith(("_Proxy", "Proxy", "NoOp", "_NoOp", "Default")) + + +def _instrumentor_names(areas: Dict[str, bool]) -> List[str]: + names: List[str] = [] + if areas.get("http"): + names.extend(_HTTP_CLIENT_INSTRUMENTORS + _HTTP_SERVER_INSTRUMENTORS) + if areas.get("db"): + names.extend(_DB_INSTRUMENTORS) + if areas.get("runtime"): + names.append("system_metrics") + return names + + +def start_metrics_autocapture( + *, + host: str, + api_key: str, + areas: Dict[str, bool], + service_name: Optional[str], + resource_attributes: Dict[str, Any], + is_enabled: Callable[[], bool], + export_interval_seconds: Optional[float] = None, + before_send: Optional[Callable] = None, + _metric_reader: Any = None, +) -> Optional[MetricsAutocapture]: + """Starts metrics autocapture. Returns ``None``, after one warning, when the + OpenTelemetry packages are missing, when OpenTelemetry is already set up in + the process, or when autocapture is already running. Never raises. + + ``before_send`` is the ``metrics.before_send`` hook. It gets each + measurement as a ``client.metrics`` sample and can change its value and + attributes, or drop it.""" + global _active, _warned_about_missing_packages + host = host.rstrip("/") + with _lock: + if _active is not None: + log.warning( + "Metrics autocapture is already running in this process, so this client does not start it again." + ) + return None + try: + metrics_api = _import_module("opentelemetry.metrics") + trace_api = _import_module("opentelemetry.trace") + sdk_metrics = _import_module("opentelemetry.sdk.metrics") + sdk_metrics_export = _import_module("opentelemetry.sdk.metrics.export") + sdk_trace = _import_module("opentelemetry.sdk.trace") + sampling = _import_module("opentelemetry.sdk.trace.sampling") + sdk_resources = _import_module("opentelemetry.sdk.resources") + otlp = _import_module( + "opentelemetry.exporter.otlp.proto.http.metric_exporter" + ) + _import_module("opentelemetry.instrumentation") + except ImportError as e: + if not _warned_about_missing_packages: + _warned_about_missing_packages = True + log.warning( + "Metrics autocapture needs the OpenTelemetry packages. Install them with: %s (%s)", + _INSTALL_HINT, + e, + ) + return None + + reader: Any = None + meter_provider: Any = None + tracer_provider: Any = None + instrumentors: List[Any] = [] + stopped = threading.Event() + try: + if not _is_default_provider( + metrics_api.get_meter_provider() + ) or not _is_default_provider(trace_api.get_tracer_provider()): + log.warning( + "OpenTelemetry is already set up in this process, so metrics autocapture does not start. " + "Point your OTLP metrics exporter at PostHog instead: https://posthog.com/docs/metrics" + ) + return None + + # The same resource as `client.metrics`: the OTEL_SERVICE_NAME and + # OTEL_RESOURCE_ATTRIBUTES of the app are not read. + resource = sdk_resources.Resource( + { + **resource_attributes, + "service.name": service_name or "unknown_service", + "telemetry.sdk.language": "python", + "telemetry.sdk.name": "opentelemetry", + "telemetry.sdk.version": _import_module( + "opentelemetry.sdk.version" + ).__version__, + "telemetry.distro.name": "posthog-python", + "telemetry.distro.version": VERSION, + } + ) + + reader = _metric_reader + if reader is None: + exporter_base: Any = otlp.OTLPMetricExporter + delta = sdk_metrics_export.AggregationTemporality.DELTA + cumulative = sdk_metrics_export.AggregationTemporality.CUMULATIVE + + class _GatedExporter(exporter_base): + # The exporter still sends while the client is disabled unless it is gated here. + def export(self, metrics_data, timeout_millis=10_000, **kwargs): + if not is_enabled(): + return sdk_metrics_export.MetricExportResult.SUCCESS + return super().export( + metrics_data, timeout_millis=timeout_millis, **kwargs + ) + + exporter = _GatedExporter( + endpoint=f"{host}/i/v1/metrics", + headers={"Authorization": f"Bearer {api_key}"}, + timeout=_EXPORT_TIMEOUT_SECONDS, + compression=_import_module( + "opentelemetry.exporter.otlp.proto.http" + ).Compression.NoCompression, + session=_metrics_export_session(api_key), + # Delta, like the `posthog.metrics` wire shape; values that go + # up and down stay cumulative. + preferred_temporality={ + sdk_metrics.Counter: delta, + sdk_metrics.Histogram: delta, + sdk_metrics.ObservableCounter: delta, + sdk_metrics.UpDownCounter: cumulative, + sdk_metrics.ObservableUpDownCounter: cumulative, + sdk_metrics.ObservableGauge: cumulative, + }, + preferred_aggregation={ + sdk_metrics.Histogram: _import_module( + "opentelemetry.sdk.metrics.view" + ).ExplicitBucketHistogramAggregation(), + }, + ) + reader = sdk_metrics_export.PeriodicExportingMetricReader( + exporter, + export_interval_millis=(export_interval_seconds or 10.0) * 1000, + ) + # The client exports the last window at exit, within its exit budget. + # The provider's own exit hook waits up to the export timeout, also + # when the PostHog host does not answer. + meter_provider = sdk_metrics.MeterProvider( + resource=resource, metric_readers=[reader], shutdown_on_exit=False + ) + + sampler_base: Any = sampling.Sampler + + class _ClientSpansOnly(sampler_base): + # Records only client spans, the ones a database call makes. Database + # instrumentors set `db.system` after start, so the kind is all a + # sampler can check. Server spans stay no-ops. + def should_sample( + self, + parent_context, + trace_id, + name, + kind=None, + attributes=None, + links=None, + trace_state=None, + ): + if kind != trace_api.SpanKind.CLIENT: + return sampling.SamplingResult(sampling.Decision.DROP) + # A recorded span starts with these attributes only. Some + # instrumentors, such as asyncpg, pass `db.system` here. + return sampling.SamplingResult( + sampling.Decision.RECORD_ONLY, attributes + ) + + def get_description(self): + return "PostHogClientSpansOnly" + + bounded_meter_provider = _BoundedMeterProvider( + meter_provider, _MAX_ATTRIBUTE_SETS, is_enabled, before_send, stopped + ) + tracer_provider = sdk_trace.TracerProvider( + resource=resource, sampler=_ClientSpansOnly(), shutdown_on_exit=False + ) + # A span of the private provider is still the current span, so an HTTP + # client would send its trace context to other services (as "not + # sampled", which makes them drop their traces). HTTP metrics do not + # need spans. + no_op_tracer_provider = trace_api.NoOpTracerProvider() + if areas.get("db"): + tracer_provider.add_span_processor( + DbSpanMetricsProcessor( + bounded_meter_provider.get_meter( + "posthog.metrics_autocapture", VERSION + ) + ) + ) + + posthog_url_pattern = _posthog_url_pattern(host) + wanted = set(_instrumentor_names(areas)) + with _stable_http_semconv(): + for entry_point in entry_points(group="opentelemetry_instrumentor"): + if entry_point.name not in wanted: + continue + if ( + entry_point.name == "django" + and not _django_settings_configured() + ): + # Without settings, the Django instrumentor configures empty + # ones, and the app's own settings.configure() then fails. + # In AppConfig.ready() the settings are configured. + log.debug( + "Metrics autocapture skipped django: its settings are not configured yet" + ) + continue + try: + kwargs: Dict[str, Any] = { + "tracer_provider": tracer_provider + if entry_point.name in _DB_INSTRUMENTORS + else no_op_tracer_provider, + "meter_provider": bounded_meter_provider, + } + environ: Dict[str, Optional[str]] = {} + if entry_point.name in _HTTP_CLIENT_INSTRUMENTORS: + # PostHog's own uploads, including this exporter's, are not app traffic. + variable, excluded = _excluded_urls( + entry_point.name, posthog_url_pattern + ) + kwargs["excluded_urls"] = excluded + environ[variable] = excluded + with _patched_environ(environ): + instrumentor_class = entry_point.load() + # Instrumentors are singletons, and creating one runs + # `__init__` again on the existing instance. That would + # reset the config of an instance that the app uses. + existing = getattr(instrumentor_class, "_instance", None) + if ( + existing is not None + and existing.is_instrumented_by_opentelemetry + ): + continue + instrumentor = ( + instrumentor_class(config=_RUNTIME_CONFIG) + if entry_point.name == "system_metrics" + else instrumentor_class() + ) + if instrumentor.is_instrumented_by_opentelemetry: + # The app instrumented this library itself. + continue + instrumentor.instrument(**kwargs) + if instrumentor.is_instrumented_by_opentelemetry: + instrumentors.append(instrumentor) + _disable_propagation(instrumentor_class) + if entry_point.name == "aiohttp-client": + _gate_aiohttp_trace_configs( + bounded_meter_provider, stopped, posthog_url_pattern + ) + if entry_point.name == "flask": + _track_flask_apps( + instrumentor_class, + { + "tracer_provider": no_op_tracer_provider, + "meter_provider": bounded_meter_provider, + }, + ) + except Exception: + log.debug( + "Metrics autocapture skipped %s", + entry_point.name, + exc_info=True, + ) + + _active = MetricsAutocapture( + meter_provider, tracer_provider, instrumentors, reader, stopped + ) + return _active + except Exception: + log.exception("Metrics autocapture failed to start") + # Each start attempt would otherwise leave an export thread behind. + stopped.set() + _uninstrument(instrumentors) + _shut_down(tracer_provider, meter_provider or reader) + return None diff --git a/posthog/client.py b/posthog/client.py index ff91090a..6b078616 100644 --- a/posthog/client.py +++ b/posthog/client.py @@ -21,7 +21,12 @@ from posthog._async_utils import _BackgroundEventLoopRunner from posthog._disabled_lane_queue import _DisabledLaneQueue from posthog.args import ID_TYPES, ExceptionArg, OptionalCaptureArgs, OptionalSetArgs -from posthog.metrics_capture import PostHogMetrics +from posthog.metrics_capture import ( + _DEFAULT_FLUSH_INTERVAL_SECONDS, + PostHogMetrics, + _validated_before_send, + _validated_flush_interval, +) from posthog.tracing._config import resolve_traces_config from posthog.tracing._drops import DropLog from posthog.tracing._export import SpanExporter @@ -982,10 +987,16 @@ def __init__( self._lifecycle_callback_context: ContextVar[bool] = ContextVar( "posthog_lifecycle_callback", default=False ) + # Set while a metrics `before_send` hook runs: a flush it calls does not + # start another autocapture export, which would run the hook again. + self._skip_metrics_autocapture_flush: ContextVar[bool] = ContextVar( + "posthog_skip_metrics_autocapture_flush", default=False + ) self._deferred_flush_lock = threading.Lock() self._deferred_flush_pending = False self._deferred_flush_followup = False self._deferred_flush_followup_timeout: Optional[float] = None + self._deferred_flush_followup_metrics = False # Used for session replay URL generation - we don't want the server host here. self.raw_host = normalize_host(host) self.host = determine_server_host(host) @@ -1222,6 +1233,10 @@ def __init__( ) self._lanes = [self._analytics_lane, self._ai_lane] + self._metrics_autocapture: Optional[Any] = None + self._metrics_autocapture_tried = False + self._start_metrics_autocapture_once(metrics) + if hasattr(os, "register_at_fork"): weak_self = weakref.ref(self) os.register_at_fork( @@ -1230,6 +1245,81 @@ def __init__( self._warn_if_duplicate_async_client() + def _start_metrics_autocapture_once(self, metrics_config: Any) -> None: + """Starts metrics autocapture when the ``metrics`` option asks for it. A + client tries once, so a module-level ``setup()`` on each call does not + start it again or repeat a warning.""" + autocapture = ( + metrics_config.get("autocapture") + if isinstance(metrics_config, dict) + else None + ) + if not autocapture or self.disabled or self._metrics_autocapture_tried: + return + self._metrics_autocapture_tried = True + self._metrics_autocapture = self._start_metrics_autocapture( + metrics_config, autocapture if isinstance(autocapture, dict) else {} + ) + # Sync mode has no exit hook for events; the window needs its own. + if self._metrics_autocapture is not None and self.sync_mode and self.send: + atexit.register(self._atexit_metrics_autocapture) + + def _start_metrics_autocapture(self, metrics_config: dict, areas: dict): + # Imported here, so a client without autocapture never loads OpenTelemetry. + from ._metrics_autocapture import start_metrics_autocapture + + resource_attributes = metrics_config.get("resource_attributes") + resource_attributes = ( + dict(resource_attributes) if isinstance(resource_attributes, dict) else {} + ) + for key, attribute in ( + ("service_version", "service.version"), + ("environment", "deployment.environment"), + ): + if metrics_config.get(key) and attribute not in resource_attributes: + resource_attributes[attribute] = metrics_config[key] + weak_self = weakref.ref(self) + before_send = _validated_before_send(metrics_config.get("before_send")) + if before_send is not None: + hook = before_send + + def run_as_callback(sample): + # The hook runs while OpenTelemetry collects or records. As an + # SDK callback, a flush() or shutdown() it calls is deferred, so + # it does not wait for that same collection. + client = weak_self() + if client is None: + return hook(sample) + callback_token = client._lifecycle_callback_context.set(True) + skip_token = client._skip_metrics_autocapture_flush.set(True) + try: + return hook(sample) + finally: + client._skip_metrics_autocapture_flush.reset(skip_token) + client._lifecycle_callback_context.reset(callback_token) + + before_send = run_as_callback + + return start_metrics_autocapture( + host=self.host, + api_key=self.api_key, + areas={ + "http": areas.get("http", True), + "db": areas.get("db", True), + "runtime": areas.get("runtime", True), + }, + service_name=resource_attributes.get("service.name") + or metrics_config.get("service_name"), + resource_attributes=resource_attributes, + is_enabled=lambda: (client := weak_self()) is not None + and not client.disabled + and client.send, + export_interval_seconds=_validated_flush_interval( + metrics_config.get("flush_interval", _DEFAULT_FLUSH_INTERVAL_SECONDS) + ), + before_send=before_send, + ) + @property def queue(self) -> Queue: """The analytics lane's queue (kept for backwards compatibility).""" @@ -2462,10 +2552,14 @@ def _reinit_after_fork(self): self._lifecycle_callback_context = ContextVar( "posthog_lifecycle_callback", default=False ) + self._skip_metrics_autocapture_flush = ContextVar( + "posthog_skip_metrics_autocapture_flush", default=False + ) self._deferred_flush_lock = threading.Lock() self._deferred_flush_pending = False self._deferred_flush_followup = False self._deferred_flush_followup_timeout = None + self._deferred_flush_followup_metrics = False # Async runner threads do not survive fork(); recreate lazily on next async cache call. self._flag_definition_cache_provider_async_runner = None @@ -2895,7 +2989,7 @@ def get_active_span(self) -> Optional[Span]: def flush(self, timeout_seconds: Optional[float] = 10) -> None: """ - Attempt immediate delivery of queued events and ended spans, keeping the client reusable. + Attempt immediate delivery of queued events, ended spans and autocaptured metrics, keeping the client reusable. For a client shared across requests or serverless invocations (including the module-level client), call ``flush()`` before a short-lived invocation @@ -2932,12 +3026,22 @@ def flush(self, timeout_seconds: Optional[float] = 10) -> None: posthog.flush(timeout_seconds=10) ``` """ - if self._defer_flush_from_callback(timeout_seconds): + include_metrics_autocapture = not self._skip_metrics_autocapture_flush.get() + if self._defer_flush_from_callback( + timeout_seconds, include_metrics_autocapture + ): return try: # Spans drain with events: serverless handlers call flush(), not # shutdown(), and leaving spans on their own timer would lose them. span_flush = self._start_span_flush(timeout_seconds) + # Autocaptured metrics would otherwise wait for their export timer. + metrics_flush = ( + self._start_metrics_autocapture_flush() + if include_metrics_autocapture + else None + ) + deadline: Optional[float] = None with self._drain_lanes_together(): if timeout_seconds is None: for lane in self._lanes: @@ -2946,6 +3050,10 @@ def flush(self, timeout_seconds: Optional[float] = 10) -> None: deadline = time.monotonic() + timeout_seconds for lane in self._lanes: lane.flush(max(0.0, deadline - time.monotonic())) + if metrics_flush is not None: + metrics_flush( + None if deadline is None else max(0.0, deadline - time.monotonic()) + ) if span_flush is not None: # The last span request is bounded only by the request # timeout, so the wait is not. @@ -2954,6 +3062,12 @@ def flush(self, timeout_seconds: Optional[float] = 10) -> None: self.log.exception("error flushing queue: %s", e) return + def _start_metrics_autocapture_flush( + self, + ) -> Optional[Callable[[Optional[float]], None]]: + autocapture = self._metrics_autocapture + return autocapture.start_flush() if autocapture is not None else None + def _start_span_flush( self, timeout_seconds: Optional[float] ) -> Optional[Callable[[Optional[float]], None]]: @@ -3033,7 +3147,9 @@ def run() -> None: self._start_lifecycle_thread(run, "lifecycle") return True - def _defer_flush_from_callback(self, timeout_seconds: Optional[float]) -> bool: + def _defer_flush_from_callback( + self, timeout_seconds: Optional[float], include_metrics_autocapture: bool = True + ) -> bool: if not self._is_lifecycle_callback_thread(): return False with self._deferred_flush_lock: @@ -3041,7 +3157,12 @@ def _defer_flush_from_callback(self, timeout_seconds: Optional[float]) -> bool: if not self._deferred_flush_followup: self._deferred_flush_followup = True self._deferred_flush_followup_timeout = timeout_seconds - elif ( + self._deferred_flush_followup_metrics = include_metrics_autocapture + return True + self._deferred_flush_followup_metrics = ( + self._deferred_flush_followup_metrics or include_metrics_autocapture + ) + if ( self._deferred_flush_followup_timeout is not None and timeout_seconds is not None ): @@ -3055,13 +3176,20 @@ def _defer_flush_from_callback(self, timeout_seconds: Optional[float]) -> bool: def run() -> None: next_timeout = timeout_seconds + next_metrics = include_metrics_autocapture while True: - self.flush(next_timeout) + skip_token = self._skip_metrics_autocapture_flush.set(not next_metrics) + try: + self.flush(next_timeout) + finally: + self._skip_metrics_autocapture_flush.reset(skip_token) with self._deferred_flush_lock: if self._deferred_flush_followup: next_timeout = self._deferred_flush_followup_timeout + next_metrics = self._deferred_flush_followup_metrics self._deferred_flush_followup = False self._deferred_flush_followup_timeout = None + self._deferred_flush_followup_metrics = False continue self._deferred_flush_pending = False return @@ -3202,6 +3330,15 @@ def _shutdown_once(self, errors: list[Exception]) -> None: self._run_lifecycle_cleanup( "Failed to reset metrics on shutdown", self._metrics.reset, errors ) + if self._metrics_autocapture is not None: + # Exports the last window, then removes the instrumentation. + self._run_lifecycle_cleanup( + "Failed to shut down metrics autocapture", + self._metrics_autocapture.shutdown, + errors, + ) + self._metrics_autocapture = None + atexit.unregister(self._atexit_metrics_autocapture) with self._traces_lock: traces = self._traces if traces is not None: @@ -3303,8 +3440,11 @@ def _atexit(self) -> None: span_flush = self._start_span_flush( max(0.0, deadline - time.monotonic()) ) + metrics_flush = self._start_metrics_autocapture_flush() for lane in self._lanes: lane.flush(max(0.0, deadline - time.monotonic())) + if metrics_flush is not None: + metrics_flush(max(0.0, deadline - time.monotonic())) self._join_span_flush(span_flush, deadline) finally: # Consumers are daemon threads. Publish a non-draining stop to @@ -3317,6 +3457,12 @@ def _atexit(self) -> None: self._lifecycle_owner = None self._lifecycle_condition.notify_all() + @no_throw() + def _atexit_metrics_autocapture(self) -> None: + waiter = self._start_metrics_autocapture_flush() + if waiter is not None: + waiter(max(0.0, _get_atexit_deadline() - time.monotonic())) + @no_throw() def _atexit_spans(self) -> None: # The span timer is a daemon thread that dies at exit. diff --git a/posthog/metrics_capture.py b/posthog/metrics_capture.py index 781aa89b..3d8bc15d 100644 --- a/posthog/metrics_capture.py +++ b/posthog/metrics_capture.py @@ -272,6 +272,28 @@ def __init__( self.exemplar: Optional[dict] = None +def _validated_flush_interval(flush_interval: Any) -> float: + # Shared with metrics autocapture, which reads the same option. + if ( + not isinstance(flush_interval, (int, float)) + or isinstance(flush_interval, bool) + or not flush_interval > 0 + ): + log.warning( + "Ignoring metrics flush_interval %r: expected a positive number of seconds", + flush_interval, + ) + return _DEFAULT_FLUSH_INTERVAL_SECONDS + return float(flush_interval) + + +def _validated_before_send(before_send: Any) -> Optional[Callable]: + if before_send is not None and not callable(before_send): + log.warning("Ignoring metrics before_send: expected a callable") + return None + return before_send + + class PostHogMetrics: """The ``client.metrics`` API: ``count``, ``gauge``, ``histogram``, ``flush``. @@ -318,18 +340,9 @@ def __init__(self, client, config: Optional[dict] = None): "deployment.environment" ) or config.get("environment") self._resource_attributes: dict = resource_attributes - flush_interval = config.get("flush_interval", _DEFAULT_FLUSH_INTERVAL_SECONDS) - if ( - not isinstance(flush_interval, (int, float)) - or isinstance(flush_interval, bool) - or not flush_interval > 0 - ): - log.warning( - "Ignoring metrics flush_interval %r: expected a positive number of seconds", - flush_interval, - ) - flush_interval = _DEFAULT_FLUSH_INTERVAL_SECONDS - self._flush_interval: float = float(flush_interval) + self._flush_interval: float = _validated_flush_interval( + config.get("flush_interval", _DEFAULT_FLUSH_INTERVAL_SECONDS) + ) max_series = config.get("max_series_per_flush", _DEFAULT_MAX_SERIES_PER_FLUSH) if ( not isinstance(max_series, int) @@ -342,11 +355,9 @@ def __init__(self, client, config: Optional[dict] = None): ) max_series = _DEFAULT_MAX_SERIES_PER_FLUSH self._max_series_per_flush: int = max_series - before_send = config.get("before_send") - if before_send is not None and not callable(before_send): - log.warning("Ignoring metrics before_send: expected a callable") - before_send = None - self._before_send: Optional[Callable] = before_send + self._before_send: Optional[Callable] = _validated_before_send( + config.get("before_send") + ) self._lock = threading.Lock() self._pid = os.getpid() diff --git a/posthog/test/ai/langchain/test_callbacks.py b/posthog/test/ai/langchain/test_callbacks.py index 04f8e666..91655d5e 100644 --- a/posthog/test/ai/langchain/test_callbacks.py +++ b/posthog/test/ai/langchain/test_callbacks.py @@ -1403,9 +1403,10 @@ def test_tool_calls(mock_client): ) -async def test_async_traces(mock_client): +@pytest.mark.parametrize("delay", [0, 0.1]) +async def test_async_traces(mock_client, delay): async def sleep(x): # -> Any: - await asyncio.sleep(0.1) + await asyncio.sleep(delay) return x prompt = ChatPromptTemplate.from_messages([("user", "Foo")]) @@ -1422,18 +1423,22 @@ async def sleep(x): # -> Any: approximate_latency = math.floor(time.time() - start_time) assert mock_client.capture_ai.call_count == 4 - first_call, second_call, third_call, fourth_call = ( - mock_client.capture_ai.call_args_list - ) - assert first_call[1]["event"] == "$ai_span" - assert second_call[1]["event"] == "$ai_generation" - assert third_call[1]["event"] == "$ai_trace" - assert third_call[1]["properties"]["$ai_span_name"] == "RunnableSequence" - assert fourth_call[1]["event"] == "$ai_trace" - assert fourth_call[1]["properties"]["$ai_span_name"] == "sleep" + calls = [call.kwargs for call in mock_client.capture_ai.call_args_list] + assert sorted(call["event"] for call in calls) == [ + "$ai_generation", + "$ai_span", + "$ai_trace", + "$ai_trace", + ] + traces = { + call["properties"]["$ai_span_name"]: call["properties"] + for call in calls + if call["event"] == "$ai_trace" + } + assert set(traces) == {"RunnableSequence", "sleep"} assert ( min(approximate_latency - 1, 0) - <= math.floor(third_call[1]["properties"]["$ai_latency"]) + <= math.floor(traces["RunnableSequence"]["$ai_latency"]) <= approximate_latency ) diff --git a/posthog/test/test_metrics_autocapture.py b/posthog/test/test_metrics_autocapture.py new file mode 100644 index 00000000..e69310b4 --- /dev/null +++ b/posthog/test/test_metrics_autocapture.py @@ -0,0 +1,1716 @@ +import os +import socket +import sqlite3 +import subprocess +import sys +import textwrap +import threading +import time +from contextlib import ExitStack, contextmanager +from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer +from pathlib import Path +from unittest import mock + +import aiohttp +import flask +import httpx +import opentelemetry.propagate +import pytest +import requests +from opentelemetry import baggage, context, trace +from opentelemetry.instrumentation._semconv import ( + _OpenTelemetrySemanticConventionStability, +) +from opentelemetry.instrumentation.requests import RequestsInstrumentor +from opentelemetry.instrumentation.system_metrics import SystemMetricsInstrumentor +from opentelemetry.sdk.metrics import Histogram, MeterProvider +from opentelemetry.sdk.metrics.export import ( + AggregationTemporality, + InMemoryMetricReader, +) +from opentelemetry.sdk.trace import TracerProvider +from opentelemetry.trace import SpanKind, Status, StatusCode +from opentelemetry.util.http import parse_excluded_urls + +from posthog import _metrics_autocapture as metrics_autocapture +from posthog.client import Client +from posthog._metrics_autocapture import ( + DbSpanMetricsProcessor, + _BoundedInstrument, + _BoundedMeterProvider, + start_metrics_autocapture, +) + +FAKE_API_KEY = "phc_test_key" + + +class _Server: + def __init__(self): + received = self.received = [] + + class Handler(BaseHTTPRequestHandler): + def _respond(self): + length = int(self.headers.get("content-length") or 0) + received.append( + { + "path": self.path, + "headers": dict(self.headers), + "body": self.rfile.read(length), + } + ) + self.send_response(200) + self.send_header("content-type", "application/json") + self.send_header("content-length", "2") + self.end_headers() + self.wfile.write(b"{}") + + do_GET = _respond + do_POST = _respond + + def log_message(self, *args): + pass + + self.httpd = ThreadingHTTPServer(("127.0.0.1", 0), Handler) + self.port = self.httpd.server_address[1] + self.url = f"http://127.0.0.1:{self.port}" + threading.Thread(target=self.httpd.serve_forever, daemon=True).start() + + def close(self): + self.httpd.shutdown() + self.httpd.server_close() + + +@pytest.fixture +def posthog_host(): + server = _Server() + yield server + server.close() + + +@pytest.fixture +def app_server(): + server = _Server() + yield server + server.close() + + +@pytest.fixture +def started(): + handles = [] + yield handles + for handle in handles: + handle.shutdown() + + +@pytest.fixture(autouse=True) +def _reset_autocapture(): + # A failed test must not leave autocapture running for the tests after it. + yield + active = metrics_autocapture._active + if active is not None: + active.shutdown() + metrics_autocapture._warned_about_missing_packages = False + + +class _ObservedLock: + """A lock that sets ``waited`` when a thread has to wait for it.""" + + def __init__(self, waited: threading.Event): + self._lock = threading.Lock() + self._waited = waited + + def __enter__(self): + if not self._lock.acquire(blocking=False): + self._waited.set() + self._lock.acquire() + return self + + def __exit__(self, *exc_info): + self._lock.release() + + +@contextmanager +def _held_by_another_thread(lock): + # A forked child has no copy of this thread, so the lock stays held there. + held, release = threading.Event(), threading.Event() + + def hold(): + with lock: + held.set() + release.wait() + + thread = threading.Thread(target=hold, daemon=True) + thread.start() + held.wait() + try: + yield + finally: + release.set() + thread.join() + + +def _child_result(pid, read_fd, timeout=20): + deadline = time.monotonic() + timeout + while time.monotonic() < deadline: + if os.waitpid(pid, os.WNOHANG) != (0, 0): + break + time.sleep(0.05) + else: + os.kill(pid, 9) + os.waitpid(pid, 0) + result = os.read(read_fd, 16) + os.close(read_fd) + return result + + +def _metrics_exports(server): + return [r for r in server.received if r["path"].endswith("/i/v1/metrics")] + + +def _start(posthog_host, started, reader=None, **overrides): + options = dict( + host=posthog_host.url, + api_key=FAKE_API_KEY, + areas={"http": True, "db": True, "runtime": True}, + service_name="checkout-api", + resource_attributes={"deployment.environment": "test"}, + is_enabled=lambda: True, + ) + options.update(overrides) + handle = start_metrics_autocapture(**options, _metric_reader=reader) + if handle is not None: + started.append(handle) + return handle + + +def _points(reader, name): + data = reader.get_metrics_data() + points = [] + for resource_metrics in data.resource_metrics if data else []: + for scope_metrics in resource_metrics.scope_metrics: + for metric in scope_metrics.metrics: + if metric.name == name: + points.extend(metric.data.data_points) + return points + + +def _names(reader): + data = reader.get_metrics_data() + return { + metric.name + for resource_metrics in (data.resource_metrics if data else []) + for scope_metrics in resource_metrics.scope_metrics + for metric in scope_metrics.metrics + } + + +class TestStartMetricsAutocapture: + def test_records_http_client_durations_but_not_requests_to_posthog( + self, posthog_host, app_server, started + ): + reader = InMemoryMetricReader() + assert _start(posthog_host, started, reader) is not None + + requests.get(f"{app_server.url}/health") + requests.post(f"{posthog_host.url}/batch/", data="{}") + + ports = [ + point.attributes.get("server.port") + for point in _points(reader, "http.client.request.duration") + ] + assert app_server.port in ports + assert posthog_host.port not in ports + + def test_records_database_operations_without_the_statement( + self, posthog_host, started + ): + reader = InMemoryMetricReader() + _start(posthog_host, started, reader) + + # The instrumentor wraps cursors, which drivers and SQLAlchemy use. + connection = sqlite3.connect(":memory:") + cursor = connection.cursor() + cursor.execute("CREATE TABLE orders (email TEXT)") + cursor.execute("SELECT * FROM orders WHERE email = 'a@b.c'") + connection.close() + + points = _points(reader, "db.client.operation.duration") + operations = {point.attributes.get("db.operation.name") for point in points} + assert {"CREATE", "SELECT"} <= operations + for point in points: + assert point.attributes["db.system.name"] == "sqlite" + assert all("a@b.c" not in str(value) for value in point.attributes.values()) + + def test_records_runtime_metrics(self, posthog_host, started): + reader = InMemoryMetricReader() + _start( + posthog_host, + started, + reader, + areas={"http": False, "db": False, "runtime": True}, + ) + + names = _names(reader) + assert "process.cpu.time" in names + assert any(name.startswith("cpython.gc.") for name in names) + assert "http.client.request.duration" not in names + + def test_exports_otlp_to_posthog_with_the_project_token( + self, posthog_host, app_server, started + ): + handle = _start(posthog_host, started) + requests.get(f"{app_server.url}/health") + + handle.force_flush() + + exports = [r for r in posthog_host.received if r["path"] == "/i/v1/metrics"] + assert exports + headers = {k.lower(): v for k, v in exports[0]["headers"].items()} + assert headers["authorization"] == f"Bearer {FAKE_API_KEY}" + assert headers["content-type"] == "application/x-protobuf" + body = b"".join(r["body"] for r in exports) + for expected in ( + b"http.client.request.duration", + b"checkout-api", + b"telemetry.distro.name", + b"posthog-python", + b"deployment.environment", + ): + assert expected in body + + def test_sends_nothing_while_opted_out(self, posthog_host, app_server, started): + handle = _start(posthog_host, started, is_enabled=lambda: False) + requests.get(f"{app_server.url}/health") + + handle.force_flush() + + assert [r for r in posthog_host.received if r["path"] == "/i/v1/metrics"] == [] + + def test_warns_once_and_returns_none_when_packages_are_missing( + self, posthog_host, started + ): + def missing(name): + raise ImportError(f"No module named '{name}'") + + with mock.patch("posthog._metrics_autocapture._import_module", missing): + with mock.patch("posthog._metrics_autocapture.log") as log: + assert _start(posthog_host, started) is None + assert _start(posthog_host, started) is None + + assert log.warning.call_count == 1 + assert "posthog[metrics]" in str(log.warning.call_args) + + def test_does_not_start_when_opentelemetry_is_already_set_up( + self, posthog_host, started + ): + with mock.patch( + "opentelemetry.metrics.get_meter_provider", return_value=MeterProvider() + ): + with mock.patch("posthog._metrics_autocapture.log") as log: + assert _start(posthog_host, started) is None + + assert "already set up" in str(log.warning.call_args) + + def test_runs_once_per_process_and_can_start_again_after_shutdown( + self, posthog_host, started + ): + first = _start(posthog_host, started, InMemoryMetricReader()) + with mock.patch("posthog._metrics_autocapture.log") as log: + assert _start(posthog_host, started) is None + assert log.warning.call_count == 1 + + first.shutdown() + reader = InMemoryMetricReader() + assert _start(posthog_host, started, reader) is not None + + connection = sqlite3.connect(":memory:") + connection.cursor().execute("SELECT 1") + connection.close() + assert _points(reader, "db.client.operation.duration") + + def test_does_not_reconfigure_runtime_metrics_that_the_app_set_up( + self, posthog_host, started + ): + # The app's meter provider is private, so autocapture still starts. + app_reader = InMemoryMetricReader() + app_instrumentor = SystemMetricsInstrumentor( + config={"system.cpu.time": ["idle", "user"]} + ) + app_instrumentor.instrument( + meter_provider=MeterProvider(metric_readers=[app_reader]) + ) + try: + assert ( + _start( + posthog_host, + started, + InMemoryMetricReader(), + areas={"runtime": True}, + ) + is not None + ) + assert "system.cpu.time" in _names(app_reader) + finally: + app_instrumentor.uninstrument() + + def test_a_client_that_starts_during_shutdown_records_http_metrics( + self, posthog_host, app_server, started, monkeypatch + ): + # Set when the other client has started, or has to wait for this shutdown. + progress = threading.Event() + monkeypatch.setattr(metrics_autocapture, "_lock", _ObservedLock(progress)) + first = _start(posthog_host, started, areas={"http": True}) + reader = InMemoryMetricReader() + replacement = [] + + def start_replacement(): + replacement.append( + _start(posthog_host, started, reader, areas={"http": True}) + ) + progress.set() + + thread = threading.Thread(target=start_replacement) + + class StartsAnotherClientDuringShutdown(list): + def __iter__(self): + thread.start() + assert progress.wait(timeout=10) + return super().__iter__() + + first._instrumentors = StartsAnotherClientDuringShutdown(first._instrumentors) + first.shutdown() + thread.join() + + assert replacement[0] is not None + requests.get(f"{app_server.url}/health") + assert _points(reader, "http.client.request.duration") + + def test_strips_a_trailing_slash_from_the_host( + self, posthog_host, app_server, started + ): + # A reverse proxy path, as in `host="https://example.com/ingest/"`. + handle = _start(posthog_host, started, host=f"{posthog_host.url}/ingest/") + requests.get(f"{app_server.url}/health") + + handle.force_flush() + + paths = {r["path"] for r in posthog_host.received} + assert "/ingest/i/v1/metrics" in paths + assert "/ingest//i/v1/metrics" not in paths + + def test_does_not_change_the_process_environment( + self, posthog_host, started, monkeypatch + ): + monkeypatch.delenv("OTEL_SEMCONV_STABILITY_OPT_IN", raising=False) + assert _start(posthog_host, started, InMemoryMetricReader()) is not None + assert "OTEL_SEMCONV_STABILITY_OPT_IN" not in os.environ + + def test_keeps_an_opt_in_that_the_app_set(self, posthog_host, started, monkeypatch): + monkeypatch.setenv("OTEL_SEMCONV_STABILITY_OPT_IN", "http/dup") + assert _start(posthog_host, started, InMemoryMetricReader()) is not None + assert os.environ["OTEL_SEMCONV_STABILITY_OPT_IN"] == "http/dup" + + def test_uses_stable_http_names_when_opentelemetry_read_the_opt_in_before( + self, posthog_host, app_server, started, monkeypatch + ): + # Each OpenTelemetry instrumentor reads the opt-in, but only the first + # read in the process counts. + stability = _OpenTelemetrySemanticConventionStability + monkeypatch.delenv("OTEL_SEMCONV_STABILITY_OPT_IN", raising=False) + monkeypatch.setattr(stability, "_initialized", False) + monkeypatch.setattr(stability, "_OTEL_SEMCONV_STABILITY_SIGNAL_MAPPING", {}) + stability._initialize() + + reader = InMemoryMetricReader() + _start(posthog_host, started, reader, areas={"http": True}) + requests.get(f"{app_server.url}/health") + + assert _points(reader, "http.client.request.duration") + assert not _points(reader, "http.client.duration") + + def test_limits_http_attribute_sets(self, posthog_host, app_server, started): + reader = InMemoryMetricReader() + with mock.patch.object(metrics_autocapture, "_MAX_ATTRIBUTE_SETS", 1): + _start(posthog_host, started, reader) + + requests.get(f"{app_server.url}/a") + requests.post(f"{app_server.url}/b", data="{}") + + attributes = [ + dict(point.attributes) + for point in _points(reader, "http.client.request.duration") + ] + assert len(attributes) == 2 + assert {"otel.metric.overflow": True} in attributes + + @pytest.mark.skipif(not hasattr(os, "fork"), reason="needs os.fork") + @pytest.mark.filterwarnings("ignore:This process .* is multi-threaded") + def test_keeps_working_in_a_forked_child(self, posthog_host, app_server, started): + reader = InMemoryMetricReader() + handle = _start(posthog_host, started, reader) + read_fd, write_fd = os.pipe() + + with _held_by_another_thread(metrics_autocapture._lock): + pid = os.fork() + if pid == 0: # pragma: no cover - runs in the child + result = b"fail" + try: + runtime = [i for i in handle._instrumentors if hasattr(i, "_proc")] + requests.get(f"{app_server.url}/child") + if ( + runtime + and all(i._proc.pid == os.getpid() for i in runtime) + and _points(reader, "http.client.request.duration") + ): + handle.shutdown() + result = b"ok" + finally: + os.write(write_fd, result) + os._exit(0) + + os.close(write_fd) + assert _child_result(pid, read_fd) == b"ok" + # The child's shutdown does not stop the parent's autocapture. + assert metrics_autocapture._active is handle + + @pytest.mark.skipif(not hasattr(os, "fork"), reason="needs os.fork") + @pytest.mark.filterwarnings("ignore:This process .* is multi-threaded") + def test_exports_from_a_child_forked_during_an_export(self, posthog_host, started): + handle = _start(posthog_host, started, export_interval_seconds=3600) + read_fd, write_fd = os.pipe() + + # The export thread holds this lock while it sends. + with _held_by_another_thread(handle._reader._export_lock): + pid = os.fork() + if pid == 0: # pragma: no cover - runs in the child + result = b"fail" + try: + handle.force_flush() + result = b"ok" + finally: + os.write(write_fd, result) + os._exit(0) + + os.close(write_fd) + assert _child_result(pid, read_fd, timeout=15) == b"ok" + + @pytest.mark.skipif(not hasattr(os, "fork"), reason="needs os.fork") + @pytest.mark.filterwarnings("ignore:This process .* is multi-threaded") + def test_records_in_a_child_forked_during_a_collection( + self, posthog_host, app_server, started + ): + handle = _start( + posthog_host, started, export_interval_seconds=3600, areas={"http": True} + ) + requests.get(f"{app_server.url}/health") + consumer = handle._meter_provider._measurement_consumer + locks = [consumer._lock] + for storage in consumer._reader_storages.values(): + locks.append(storage._lock) + for matches in storage._instrument_view_instrument_matches.values(): + for match in matches: + locks.append(match._lock) + locks.extend( + aggregation._lock + for aggregation in match._attributes_aggregation.values() + ) + read_fd, write_fd = os.pipe() + + # Collection and recording hold these locks in other threads. + with ExitStack() as stack: + for lock in locks: + stack.enter_context(_held_by_another_thread(lock)) + pid = os.fork() + if pid == 0: # pragma: no cover - runs in the child + result = b"fail" + try: + requests.get(f"{app_server.url}/health") + handle.force_flush() + result = b"ok" + finally: + os.write(write_fd, result) + os._exit(0) + + os.close(write_fd) + assert _child_result(pid, read_fd, timeout=15) == b"ok" + + @pytest.mark.parametrize("signal", ["", "METRICS_"]) + def test_does_not_send_the_apps_otlp_settings_to_posthog( + self, posthog_host, app_server, started, monkeypatch, signal + ): + # These configure the app's own collector, not PostHog. + prefix = f"OTEL_EXPORTER_OTLP_{signal}" + for key, value in { + "HEADERS": "x-vendor-key=vendor-secret", + "COMPRESSION": "gzip", + "ENDPOINT": "http://127.0.0.1:1/other-collector", + "CERTIFICATE": "/other-collector/ca.pem", + "CLIENT_CERTIFICATE": "/other-collector/client.pem", + "CLIENT_KEY": "/other-collector/client.key", + "TIMEOUT": "invalid", + }.items(): + monkeypatch.setenv(prefix + key, value) + monkeypatch.setenv( + "OTEL_PYTHON_EXPORTER_OTLP_HTTP_METRICS_CREDENTIAL_PROVIDER", "missing" + ) + monkeypatch.setenv( + "OTEL_EXPORTER_OTLP_METRICS_DEFAULT_HISTOGRAM_AGGREGATION", + "base2_exponential_bucket_histogram", + ) + monkeypatch.setenv( + "OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE", "CUMULATIVE" + ) + handle = _start(posthog_host, started) + requests.get(f"{app_server.url}/health") + + handle.force_flush() + + exports = _metrics_exports(posthog_host) + assert exports + headers = {k.lower(): v for k, v in exports[0]["headers"].items()} + assert "x-vendor-key" not in headers + assert "content-encoding" not in headers + assert headers["authorization"] == f"Bearer {FAKE_API_KEY}" + assert os.environ[prefix + "HEADERS"] == "x-vendor-key=vendor-secret" + + from opentelemetry.proto.collector.metrics.v1.metrics_service_pb2 import ( + ExportMetricsServiceRequest, + ) + + data = ExportMetricsServiceRequest.FromString(exports[0]["body"]) + [duration] = [ + metric + for resource in data.resource_metrics + for scope in resource.scope_metrics + for metric in scope.metrics + if metric.name == "http.client.request.duration" + ] + assert duration.WhichOneof("data") == "histogram" + assert ( + duration.histogram.aggregation_temporality == AggregationTemporality.DELTA + ) + + def test_does_not_change_a_concurrent_application_exporter( + self, posthog_host, app_server, started, monkeypatch + ): + from opentelemetry.exporter.otlp.proto.http._log_exporter import OTLPLogExporter + from opentelemetry.exporter.otlp.proto.http.metric_exporter import ( + OTLPMetricExporter, + ) + + monkeypatch.setenv("OTEL_EXPORTER_OTLP_ENDPOINT", app_server.url) + monkeypatch.setenv("OTEL_EXPORTER_OTLP_HEADERS", "x-vendor-key=vendor-secret") + entered, release = threading.Event(), threading.Event() + original_init = OTLPMetricExporter.__init__ + + def paused_init(exporter, *args, **kwargs): + entered.set() + assert release.wait(10) + original_init(exporter, *args, **kwargs) + + with mock.patch.object(OTLPMetricExporter, "__init__", paused_init): + thread = threading.Thread( + target=lambda: _start( + posthog_host, started, areas={}, export_interval_seconds=3600 + ) + ) + thread.start() + try: + assert entered.wait(10) + exporter = OTLPLogExporter() + try: + assert exporter._endpoint == f"{app_server.url}/v1/logs" + exporter.export(()) + assert app_server.received[-1]["headers"]["x-vendor-key"] == ( + "vendor-secret" + ) + finally: + exporter.shutdown() + finally: + release.set() + thread.join(10) + assert not thread.is_alive() + assert started + + async def test_retained_aiohttp_session_is_inert_after_shutdown( + self, posthog_host, app_server, started + ): + reader = InMemoryMetricReader() + handle = _start(posthog_host, started, reader, areas={"http": True}) + callbacks = [] + app_config = aiohttp.TraceConfig() + + async def on_start(session, ctx, params): + callbacks.append(str(params.url)) + + app_config.on_request_start.append(on_start) + token = context.attach(baggage.set_baggage("tenant", "synthetic-canary")) + try: + async with aiohttp.ClientSession(trace_configs=[app_config]) as session: + async with session.get(f"{app_server.url}/active") as response: + assert response.status == 200 + await response.read() + [point] = _points(reader, "http.client.request.duration") + assert point.count == 1 + handle.shutdown() + async with session.get(f"{app_server.url}/stopped") as response: + assert response.status == 200 + await response.read() + assert not session.closed + assert baggage.get_baggage("tenant") == "synthetic-canary" + finally: + context.detach(token) + assert callbacks == [f"{app_server.url}/active", f"{app_server.url}/stopped"] + for request in app_server.received: + headers = {key.lower() for key in request["headers"]} + assert not headers.intersection({"baggage", "traceparent", "tracestate"}) + + def test_does_not_add_trace_context_to_app_requests( + self, posthog_host, app_server, started + ): + # The spans are never exported, so a downstream service must not see them. + reader = InMemoryMetricReader() + _start(posthog_host, started, reader) + + requests.get(f"{app_server.url}/health") + + headers = {k.lower() for k in app_server.received[-1]["headers"]} + assert "traceparent" not in headers + [point] = _points(reader, "http.client.request.duration") + assert point.attributes["http.response.status_code"] == 200 + + def test_records_nothing_while_disabled(self, posthog_host, app_server, started): + # Measurements from the disabled period must not go out once it is enabled. + enabled = [False] + reader = InMemoryMetricReader() + _start(posthog_host, started, reader, is_enabled=lambda: enabled[0]) + + requests.get(f"{app_server.url}/health") + connection = sqlite3.connect(":memory:") + connection.cursor().execute("SELECT 1") + connection.close() + enabled[0] = True + + assert not _points(reader, "http.client.request.duration") + assert not _points(reader, "db.client.operation.duration") + + def test_applies_before_send_to_measurements( + self, posthog_host, app_server, started + ): + samples = [] + + def before_send(sample): + samples.append(dict(sample)) + if sample["attributes"].get("http.request.method") == "POST": + return None + sample["attributes"] = { + key: value + for key, value in sample["attributes"].items() + if key != "server.address" + } + return sample + + reader = InMemoryMetricReader() + _start( + posthog_host, started, reader, areas={"http": True}, before_send=before_send + ) + + requests.get(f"{app_server.url}/a") + requests.post(f"{app_server.url}/b", data="{}") + + points = _points(reader, "http.client.request.duration") + assert [point.attributes["http.request.method"] for point in points] == ["GET"] + assert "server.address" not in points[0].attributes + sample = next(s for s in samples if s["name"] == "http.client.request.duration") + assert sample["type"] == "histogram" + assert sample["unit"] == "s" + assert isinstance(sample["value"], float) + + def test_drops_measurements_when_before_send_raises( + self, posthog_host, app_server, started + ): + def before_send(sample): + raise ValueError("bug in the hook") + + reader = InMemoryMetricReader() + _start( + posthog_host, started, reader, areas={"http": True}, before_send=before_send + ) + + with mock.patch("posthog._metrics_autocapture.log") as log: + response = requests.get(f"{app_server.url}/health") + + assert response.status_code == 200 + assert not _points(reader, "http.client.request.duration") + assert log.error.called + + def test_applies_before_send_to_runtime_metrics(self, posthog_host, started): + reader = InMemoryMetricReader() + _start( + posthog_host, + started, + reader, + areas={"runtime": True}, + before_send=lambda s: None if s["name"] == "process.cpu.time" else s, + ) + + names = _names(reader) + assert "process.cpu.time" not in names + assert "process.memory.usage" in names + + def test_does_not_record_requests_to_posthog_whatever_the_host_case( + self, posthog_host, started + ): + reader = InMemoryMetricReader() + host = f"http://LocalHost:{posthog_host.port}" + _start(posthog_host, started, reader, host=host) + + requests.post(f"{host}/batch/", data="{}") + + assert not _points(reader, "http.client.request.duration") + + def test_keeps_the_apps_excluded_urls( + self, posthog_host, app_server, started, monkeypatch + ): + monkeypatch.setenv("OTEL_PYTHON_REQUESTS_EXCLUDED_URLS", "healthz") + reader = InMemoryMetricReader() + _start(posthog_host, started, reader, areas={"http": True}) + + requests.get(f"{app_server.url}/healthz") + requests.post(f"{app_server.url}/orders", data="{}") + + methods = { + point.attributes["http.request.method"] + for point in _points(reader, "http.client.request.duration") + } + assert methods == {"POST"} + assert os.environ["OTEL_PYTHON_REQUESTS_EXCLUDED_URLS"] == "healthz" + + def test_does_not_record_httpx_requests_to_posthog( + self, posthog_host, app_server, started + ): + reader = InMemoryMetricReader() + _start(posthog_host, started, reader, areas={"http": True}) + + httpx.post(f"{posthog_host.url}/batch/", content="{}") + httpx.get(f"{app_server.url}/health") + + ports = [ + point.attributes.get("server.port") + for point in _points(reader, "http.client.request.duration") + ] + assert app_server.port in ports + assert posthog_host.port not in ports + + async def test_keeps_aiohttp_exclusions_for_sessions_created_after_start( + self, posthog_host, app_server, started, monkeypatch + ): + monkeypatch.setenv("OTEL_PYTHON_AIOHTTP_CLIENT_EXCLUDED_URLS", "healthz") + reader = InMemoryMetricReader() + _start(posthog_host, started, reader, areas={"http": True}) + + for _ in range(2): + async with aiohttp.ClientSession() as session: + for url in ( + f"{posthog_host.url}/batch/", + f"{app_server.url}/healthz", + f"{app_server.url}/orders", + ): + async with session.get(url) as response: + assert response.status == 200 + await response.read() + + points = _points(reader, "http.client.request.duration") + assert {point.attributes["server.port"] for point in points} == { + app_server.port + } + assert sum(point.count for point in points) == 2 + assert os.environ["OTEL_PYTHON_AIOHTTP_CLIENT_EXCLUDED_URLS"] == "healthz" + + def test_flask_restart_does_not_partially_instrument_a_served_app( + self, posthog_host, started + ): + reader = InMemoryMetricReader() + first = _start(posthog_host, started, reader, areas={"http": True}) + app = flask.Flask(__name__) + app.add_url_rule("/health", "health", lambda: ("unavailable", 503)) + client = app.test_client() + assert client.get("/health").status_code == 503 + [point] = _points(reader, "http.server.request.duration") + assert point.attributes["http.response.status_code"] == 503 + first.shutdown() + original_wsgi = app.wsgi_app + before_request = list(app.before_request_funcs.get(None, [])) + teardown_request = list(app.teardown_request_funcs.get(None, [])) + + for _ in range(3): + reader = InMemoryMetricReader() + handle = _start(posthog_host, started, reader, areas={"http": True}) + assert app.wsgi_app is original_wsgi + assert not app._is_instrumented_by_opentelemetry + assert client.get("/health").status_code == 503 + assert not _points(reader, "http.server.request.duration") + handle.shutdown() + assert app.wsgi_app is original_wsgi + assert app.before_request_funcs.get(None, []) == before_request + assert app.teardown_request_funcs.get(None, []) == teardown_request + + with pytest.raises(AssertionError): + app.before_request(lambda: None) + + def test_measures_an_existing_flask_app_restarted_before_its_first_request( + self, posthog_host, started + ): + first = _start( + posthog_host, started, InMemoryMetricReader(), areas={"http": True} + ) + app = flask.Flask(__name__) + app.add_url_rule("/health", "health", lambda: ("unavailable", 503)) + + first.shutdown() + reader = InMemoryMetricReader() + _start(posthog_host, started, reader, areas={"http": True}) + assert app.test_client().get("/health").status_code == 503 + + [point] = _points(reader, "http.server.request.duration") + assert point.count == 1 + assert point.attributes["http.response.status_code"] == 503 + + def test_shutdown_keeps_middleware_that_the_app_added(self, posthog_host, started): + samples = [] + + def before_send(sample): + samples.append(sample["name"]) + return sample + + handle = _start( + posthog_host, + started, + InMemoryMetricReader(), + areas={"http": True}, + before_send=before_send, + ) + app = flask.Flask(__name__) + app.add_url_rule("/secret", "secret", lambda: "secret") + protected = app.wsgi_app + + def require_auth(environ, start_response): + if environ.get("HTTP_AUTHORIZATION") != "token": + start_response("401 Unauthorized", [("Content-Length", "0")]) + return [b""] + return protected(environ, start_response) + + app.wsgi_app = require_auth + client = app.test_client() + assert client.get("/secret").status_code == 401 + + handle.shutdown() + samples.clear() + + assert client.get("/secret").status_code == 401 + assert ( + client.get("/secret", headers={"Authorization": "token"}).status_code == 200 + ) + # The instrumentation left inside the middleware records nothing more. + assert samples == [] + + def test_does_not_propagate_context_to_other_services( + self, posthog_host, app_server, started + ): + reader = InMemoryMetricReader() + handle = _start(posthog_host, started, reader, areas={"http": True}) + remote = trace.SpanContext( + trace_id=0x1234, + span_id=0x5678, + is_remote=True, + trace_flags=trace.TraceFlags(trace.TraceFlags.SAMPLED), + ) + # Baggage and an incoming trace, as a server instrumentor attaches them. + current = baggage.set_baggage( + "user.email", + "a@b.c", + trace.set_span_in_context(trace.NonRecordingSpan(remote)), + ) + token = context.attach(current) + try: + requests.get(f"{app_server.url}/a") + httpx.get(f"{app_server.url}/b") + finally: + context.detach(token) + + for request in app_server.received: + headers = {key.lower() for key in request["headers"]} + assert not headers & {"traceparent", "tracestate", "baggage"} + assert len(_points(reader, "http.client.request.duration")) == 2 + + handle.shutdown() + requests_instrumentation = sys.modules["opentelemetry.instrumentation.requests"] + assert requests_instrumentation.inject is opentelemetry.propagate.inject + + def test_keeps_the_attributes_a_database_span_starts_with( + self, posthog_host, started + ): + # asyncpg passes `db.system` when it starts the span, not later. + reader = InMemoryMetricReader() + handle = _start(posthog_host, started, reader, areas={"db": True}) + tracer = handle._tracer_provider.get_tracer( + "opentelemetry.instrumentation.asyncpg" + ) + + tracer.start_span( + "SELECT", + kind=SpanKind.CLIENT, + attributes={"db.system": "postgresql", "db.operation": "SELECT"}, + ).end() + + [point] = _points(reader, "db.client.operation.duration") + assert dict(point.attributes) == { + "db.system.name": "postgresql", + "db.operation.name": "SELECT", + } + + def test_coalesces_concurrent_flushes(self, posthog_host, started, monkeypatch): + handle = _start(posthog_host, started, InMemoryMetricReader()) + exporting = threading.Event() + release = threading.Event() + flushes = [] + + def slow_force_flush(*args, **kwargs): + flushes.append(threading.current_thread().name) + exporting.set() + release.wait(10) + + monkeypatch.setattr(handle._meter_provider, "force_flush", slow_force_flush) + waiters = [handle.start_flush()] + assert exporting.wait(5) + waiters += [handle.start_flush() for _ in range(4)] + flushers = [ + thread + for thread in threading.enumerate() + if thread.name == "posthog-metrics-autocapture-flush" + ] + release.set() + for wait in waiters: + wait(10) + + assert len(flushers) == 1 + # The calls made during the first flush get one more, so their + # measurements are exported too. + assert len(flushes) == 2 + + def test_a_flush_waits_only_for_the_export_after_it( + self, posthog_host, started, monkeypatch + ): + handle = _start(posthog_host, started, InMemoryMetricReader()) + exporting = [threading.Event(), threading.Event()] + release = [threading.Event(), threading.Event()] + exports = [] + + def slow_force_flush(*args, **kwargs): + export = min(len(exports), 1) + exports.append(export) + exporting[export].set() + release[export].wait(10) + + monkeypatch.setattr(handle._meter_provider, "force_flush", slow_force_flush) + first = handle.start_flush() + assert exporting[0].wait(5) + second = handle.start_flush() + release[0].set() + assert exporting[1].wait(5) + + # The second export is still running, but it is not for the first call. + began = time.monotonic() + assert first(5) + assert time.monotonic() - began < 2 + assert not second(0.1) + release[1].set() + assert second(5) + + def test_normalizes_attributes_from_before_send( + self, posthog_host, app_server, started + ): + def before_send(sample): + sample["attributes"] = { + **sample["attributes"], + "deployment.tags": ["blue", "canary"], + "owner": {"team": "core"}, + } + return sample + + reader = InMemoryMetricReader() + _start( + posthog_host, started, reader, areas={"http": True}, before_send=before_send + ) + + response = requests.get(f"{app_server.url}/health") + + assert response.status_code == 200 + [point] = _points(reader, "http.client.request.duration") + assert point.attributes["deployment.tags"] == ("blue", "canary") + assert "owner" not in point.attributes + + def test_normalizes_runtime_attributes_from_before_send( + self, posthog_host, started + ): + def before_send(sample): + sample["attributes"] = { + **sample["attributes"], + "deployment.tags": ["blue"], + "owner": {"team": "core"}, + } + return sample + + reader = InMemoryMetricReader() + _start( + posthog_host, + started, + reader, + areas={"runtime": True}, + before_send=before_send, + ) + + [point, *_] = _points(reader, "process.thread.count") + assert point.attributes["deployment.tags"] == ("blue",) + assert "owner" not in point.attributes + + def test_cleans_up_when_startup_fails(self, posthog_host, started): + real_entry_points = metrics_autocapture.entry_points + + def failing_entry_points(**kwargs): + yield from real_entry_points(**kwargs) + raise RuntimeError("broken plugin") + + def reader_threads(): + return [ + thread + for thread in threading.enumerate() + if thread.name == "OtelPeriodicExportingMetricReader" + ] + + before = len(reader_threads()) + with mock.patch.object( + metrics_autocapture, "entry_points", failing_entry_points + ): + with mock.patch("posthog._metrics_autocapture.log"): + assert _start(posthog_host, started) is None + + assert len(reader_threads()) == before + assert not RequestsInstrumentor().is_instrumented_by_opentelemetry + assert metrics_autocapture._active is None + + def test_uses_the_same_resource_as_the_manual_metrics_api( + self, posthog_host, started, monkeypatch + ): + monkeypatch.setenv("OTEL_SERVICE_NAME", "from-env") + monkeypatch.setenv("OTEL_RESOURCE_ATTRIBUTES", "team=from-env") + reader = InMemoryMetricReader() + _start( + posthog_host, + started, + reader, + areas={"runtime": True}, + service_name=None, + resource_attributes={}, + ) + + resource = reader.get_metrics_data().resource_metrics[0].resource.attributes + assert resource["service.name"] == "unknown_service" + assert "team" not in resource + assert resource["telemetry.distro.name"] == "posthog-python" + + def test_shutdown_twice_is_safe(self, posthog_host, started): + handle = _start(posthog_host, started, InMemoryMetricReader()) + + handle.shutdown() + handle.shutdown() + + assert metrics_autocapture._active is None + + +class TestPostHogUrlExclusion: + def _excluded(self, host, url): + pattern = metrics_autocapture._posthog_url_pattern(host) + return parse_excluded_urls(pattern).url_disabled(url) + + def test_matches_the_host_with_or_without_its_default_port(self): + assert self._excluded( + "https://us.i.posthog.com", "https://us.i.posthog.com:443/batch/" + ) + assert self._excluded( + "https://us.i.posthog.com:443", "https://us.i.posthog.com/batch/" + ) + assert self._excluded("http://LocalHost:8000", "http://localhost:8000/batch/") + + def test_does_not_match_other_hosts_ports_or_paths(self): + assert not self._excluded( + "https://us.i.posthog.com", "https://us.i.posthog.com.evil.com/" + ) + assert not self._excluded( + "https://us.i.posthog.com", "https://us.i.posthog.com:8443/batch/" + ) + assert not self._excluded( + "https://example.com/ingest", "https://example.com/ingestion" + ) + + def test_keeps_a_comma_in_the_host_path(self): + assert self._excluded( + "https://example.com/a,b", "https://example.com/a,b/i/v1/metrics" + ) + assert not self._excluded("https://example.com/a,b", "https://unrelated.com/b") + + +class TestBoundedMeterProvider: + def test_drops_raw_url_and_client_attributes(self): + # As the Tornado instrumentor records them: raw paths can hold tokens. + reader = InMemoryMetricReader() + provider = _BoundedMeterProvider(MeterProvider(metric_readers=[reader]), 10) + histogram = provider.get_meter("test").create_histogram("http.server.duration") + + histogram.record( + 1, + { + "http.method": "GET", + "http.status_code": 200, + "http.host": "shop.example", + "http.target": "/reset/private-token", + "http.url": "https://shop.example/reset/private-token?email=a@b.c", + "url.full": "https://shop.example/reset/private-token", + "url.path": "/reset/private-token", + "url.query": "email=a@b.c", + "http.client_ip": "203.0.113.7", + "client.address": "203.0.113.7", + "net.peer.ip": "203.0.113.7", + "http.user_agent": "Mozilla/5.0", + "user_agent.original": "Mozilla/5.0", + "http.request.header.authorization": ("Bearer secret",), + "http.response.header.set_cookie": ("session=secret",), + }, + ) + + [point] = _points(reader, "http.server.duration") + assert dict(point.attributes) == { + "http.method": "GET", + "http.status_code": 200, + "http.host": "shop.example", + } + + def test_never_raises_into_the_instrumented_call(self): + class BrokenInstrument: + def record(self, *args, **kwargs): + raise TypeError("broken") + + _BoundedInstrument(BrokenInstrument(), 10).record(1.0, {"a": "b"}) + + def test_shares_the_limit_across_wrappers_of_the_same_instrument(self): + reader = InMemoryMetricReader( + preferred_temporality={Histogram: AggregationTemporality.DELTA} + ) + native = MeterProvider(metric_readers=[reader], shutdown_on_exit=False) + provider = _BoundedMeterProvider(native, 2) + try: + for batch in (("a", "b"), ("c", "d")): + for host in batch: + histogram = provider.get_meter("test").create_histogram("duration") + histogram.record(1, {"server.address": host}) + points = _points(reader, "duration") + assert sum(point.count for point in points) == 2 + if batch == ("c", "d"): + assert [dict(point.attributes) for point in points] == [ + {"otel.metric.overflow": True} + ] + finally: + native.shutdown() + + def test_sends_new_attribute_sets_to_an_overflow_series(self): + # Delta, as autocapture exports: the SDK still keeps each attribute set. + reader = InMemoryMetricReader( + preferred_temporality={Histogram: AggregationTemporality.DELTA} + ) + provider = _BoundedMeterProvider(MeterProvider(metric_readers=[reader]), 2) + histogram = provider.get_meter("test").create_histogram("duration") + + for _ in range(2): + for host in ("a", "b", "c", "d"): + histogram.record(1, {"server.address": host}) + points = _points(reader, "duration") + assert sorted( + (str(dict(point.attributes)), point.count) for point in points + ) == [ + ("{'otel.metric.overflow': True}", 2), + ("{'server.address': 'a'}", 1), + ("{'server.address': 'b'}", 1), + ] + + +class TestDbSpanMetricsProcessor: + def _record(self, scope, kind=SpanKind.CLIENT, attributes=None, error=False): + reader = InMemoryMetricReader() + meter = MeterProvider(metric_readers=[reader]).get_meter("test") + provider = TracerProvider() + provider.add_span_processor(DbSpanMetricsProcessor(meter)) + span = provider.get_tracer(scope).start_span("query", kind=kind) + span.set_attributes(attributes or {}) + if error: + span.set_status(Status(StatusCode.ERROR)) + span.end() + return _points(reader, "db.client.operation.duration") + + def test_turns_a_driver_span_into_a_duration_histogram(self): + points = self._record( + "opentelemetry.instrumentation.psycopg2", + attributes={ + "db.system": "postgresql", + "db.statement": "select * from orders where id = 7", + "db.name": "shop", + "net.peer.name": "db.internal", + "net.peer.port": 5432, + }, + ) + + assert len(points) == 1 + assert dict(points[0].attributes) == { + "db.system.name": "postgresql", + "db.operation.name": "SELECT", + "db.namespace": "shop", + "server.address": "db.internal", + "server.port": 5432, + } + + def test_marks_failures(self): + points = self._record( + "opentelemetry.instrumentation.pymongo", + attributes={"db.system": "mongodb", "db.operation": "find"}, + error=True, + ) + + assert dict(points[0].attributes) == { + "db.system.name": "mongodb", + "db.operation.name": "find", + "error.type": "_OTHER", + } + + def test_keeps_only_the_first_word_of_the_operation(self): + points = self._record( + "opentelemetry.instrumentation.psycopg", + attributes={ + "db.system": "postgresql", + "db.operation.name": "SELECT orders", + }, + ) + + assert points[0].attributes["db.operation.name"] == "SELECT" + + def test_skips_orm_layers_and_other_spans(self): + # SQLAlchemy wraps a driver span, so counting it would count the query twice. + assert ( + self._record( + "opentelemetry.instrumentation.sqlalchemy", + attributes={"db.system": "postgresql"}, + ) + == [] + ) + assert ( + self._record( + "opentelemetry.instrumentation.psycopg2", + kind=SpanKind.SERVER, + attributes={"db.system": "postgresql"}, + ) + == [] + ) + assert ( + self._record( + "opentelemetry.instrumentation.requests", + attributes={"http.request.method": "GET"}, + ) + == [] + ) + + +class TestClientOption: + def test_starts_with_metrics_autocapture_and_stops_on_shutdown( + self, posthog_host, started + ): + client = Client( + FAKE_API_KEY, host=posthog_host.url, metrics={"autocapture": True} + ) + with mock.patch("posthog._metrics_autocapture.log"): + assert _start(posthog_host, started) is None + + client.shutdown() + assert _start(posthog_host, started) is not None + + def test_does_not_start_by_default_or_when_disabled(self, posthog_host, started): + plain = Client(FAKE_API_KEY, host=posthog_host.url) + disabled = Client( + FAKE_API_KEY, + host=posthog_host.url, + disabled=True, + metrics={"autocapture": True}, + ) + + assert _start(posthog_host, started) is not None + plain.shutdown() + disabled.shutdown() + + def test_sends_nothing_when_send_is_false(self, posthog_host, app_server): + client = Client( + FAKE_API_KEY, + host=posthog_host.url, + send=False, + metrics={"autocapture": True}, + ) + try: + assert client._metrics_autocapture is not None + requests.get(f"{app_server.url}/health") + client._metrics_autocapture.force_flush() + assert [ + r for r in posthog_host.received if r["path"] == "/i/v1/metrics" + ] == [] + finally: + client.shutdown() + + def test_passes_the_metrics_options_to_autocapture(self, posthog_host): + hook_calls = [] + + def before_send(sample): + hook_calls.append(configured._lifecycle_callback_context.get()) + return {**sample, "value": 2.0} + + with mock.patch( + "posthog._metrics_autocapture.start_metrics_autocapture", return_value=None + ) as start: + configured = Client( + FAKE_API_KEY, + host=posthog_host.url, + metrics={ + "autocapture": {"runtime": False}, + "service_name": "billing-api", + "service_version": "1.2.3", + "environment": "staging", + "resource_attributes": { + "deployment.environment": "prod", + "team": "core", + }, + "flush_interval": 30, + "before_send": before_send, + }, + ) + configured.shutdown() + Client( + FAKE_API_KEY, + host=posthog_host.url, + metrics={ + "autocapture": True, + "service_name": "billing-api", + "resource_attributes": {"service.name": "billing-worker"}, + }, + ).shutdown() + + first, second = (call.kwargs for call in start.call_args_list) + assert first["host"] == posthog_host.url + assert first["api_key"] == FAKE_API_KEY + assert first["areas"] == {"http": True, "db": True, "runtime": False} + assert first["service_name"] == "billing-api" + assert first["resource_attributes"] == { + "deployment.environment": "prod", + "team": "core", + "service.version": "1.2.3", + } + assert first["export_interval_seconds"] == 30 + # The hook runs as an SDK callback, so a flush() it calls is deferred. + assert first["before_send"]({"name": "m", "value": 1.0}) == { + "name": "m", + "value": 2.0, + } + assert hook_calls == [True] + assert second["areas"] == {"http": True, "db": True, "runtime": True} + assert second["service_name"] == "billing-worker" + assert second["export_interval_seconds"] == 10 + assert second["before_send"] is None + + @pytest.mark.parametrize("flush_interval", [-1, "5", True]) + def test_ignores_an_invalid_flush_interval(self, posthog_host, flush_interval): + client = Client( + FAKE_API_KEY, + host=posthog_host.url, + metrics={"autocapture": True, "flush_interval": flush_interval}, + ) + try: + assert client._metrics_autocapture is not None + assert client._metrics_autocapture._reader._export_interval_millis == 10_000 + finally: + client.shutdown() + + def test_flush_exports_autocaptured_metrics(self, posthog_host, app_server): + client = Client( + FAKE_API_KEY, + host=posthog_host.url, + metrics={"autocapture": True, "flush_interval": 3600}, + ) + try: + requests.get(f"{app_server.url}/health") + client.flush() + assert _metrics_exports(posthog_host) + finally: + client.shutdown() + + def test_shutdown_twice_is_safe(self, posthog_host): + client = Client( + FAKE_API_KEY, host=posthog_host.url, metrics={"autocapture": True} + ) + + client.shutdown() + client.shutdown() + + assert metrics_autocapture._active is None + + def test_exit_does_not_wait_for_an_unreachable_host(self): + # Accepts connections and never answers, like a host behind a dead proxy. + server = socket.socket() + server.bind(("127.0.0.1", 0)) + server.listen(16) + script = textwrap.dedent( + f""" + from posthog.client import Client + Client( + "{FAKE_API_KEY}", + host="http://127.0.0.1:{server.getsockname()[1]}", + metrics={{"autocapture": {{"http": False, "db": False}}}}, + ) + """ + ) + try: + began = time.monotonic() + subprocess.run( + [sys.executable, "-c", script], + check=True, + timeout=60, + cwd=Path(__file__).resolve().parents[2], + ) + elapsed = time.monotonic() - began + finally: + server.close() + + assert elapsed < 6 + + def test_sync_client_is_collectable_after_shutdown(self, posthog_host): + result = subprocess.run( + [ + sys.executable, + "-c", + textwrap.dedent( + f""" + import gc + import weakref + from posthog.client import Client + + client = Client( + "{FAKE_API_KEY}", host="{posthog_host.url}", sync_mode=True, + metrics={{"autocapture": {{"db": True, "http": False, "runtime": False}}}}, + ) + assert client._metrics_autocapture is not None + client.shutdown() + ref = weakref.ref(client) + del client + gc.collect() + assert ref() is None, "The exit callback retains the closed client" + """ + ), + ], + capture_output=True, + text=True, + timeout=20, + ) + assert result.returncode == 0, result.stderr + + def test_sync_mode_exports_the_last_window_at_exit(self, posthog_host): + script = textwrap.dedent( + f""" + import sqlite3 + from posthog.client import Client + Client( + "{FAKE_API_KEY}", + host="{posthog_host.url}", + sync_mode=True, + metrics={{ + "autocapture": {{"http": False, "runtime": False}}, + "flush_interval": 3600, + }}, + ) + connection = sqlite3.connect(":memory:") + connection.cursor().execute("SELECT 1") + connection.close() + """ + ) + subprocess.run( + [sys.executable, "-c", script], + check=True, + timeout=60, + cwd=Path(__file__).resolve().parents[2], + ) + + exports = _metrics_exports(posthog_host) + assert exports + assert b"db.client.operation.duration" in b"".join(r["body"] for r in exports) + + def test_before_send_can_call_flush(self, posthog_host): + # Runtime callbacks run while OpenTelemetry holds its collection lock. + script = textwrap.dedent( + f""" + import os, threading + from posthog.client import Client + + def before_send(sample): + if sample["name"] == "process.cpu.time": + client.flush(timeout_seconds=None) + return sample + + client = Client( + "{FAKE_API_KEY}", + host="{posthog_host.url}", + metrics={{ + "autocapture": {{"http": False, "db": False}}, + "flush_interval": 3600, + "before_send": before_send, + }}, + ) + flush = threading.Thread( + target=client.flush, kwargs={{"timeout_seconds": None}}, daemon=True + ) + flush.start() + flush.join(10) + print("deadlock" if flush.is_alive() else "ok", flush=True) + os._exit(0) + """ + ) + result = subprocess.run( + [sys.executable, "-c", script], + capture_output=True, + text=True, + timeout=60, + cwd=Path(__file__).resolve().parents[2], + ) + + assert result.stdout.strip() == "ok" + assert _metrics_exports(posthog_host) + + def _run_django_script(self, posthog_host, body): + script = textwrap.dedent( + f""" + import os + os.environ.pop("DJANGO_SETTINGS_MODULE", None) + from django.conf import settings + from posthog.client import Client + + def start(): + return Client( + "{FAKE_API_KEY}", + host="{posthog_host.url}", + metrics={{"autocapture": {{"db": False, "runtime": False}}}}, + ) + """ + ) + textwrap.dedent(body) + return subprocess.run( + [sys.executable, "-c", script], + capture_output=True, + text=True, + timeout=60, + cwd=Path(__file__).resolve().parents[2], + ) + + def test_does_not_configure_django_settings(self, posthog_host): + # Django apps that configure settings in code do it after imports. + result = self._run_django_script( + posthog_host, + """ + client = start() + settings.configure(DEBUG=True) + print("configured", settings.DEBUG) + client.shutdown() + """, + ) + + assert result.stdout.strip() == "configured True", result.stderr[-2000:] + + def test_instruments_django_once_its_settings_are_configured(self, posthog_host): + # As in AppConfig.ready(). + result = self._run_django_script( + posthog_host, + """ + settings.configure(MIDDLEWARE=[]) + client = start() + print("middleware", settings.MIDDLEWARE) + client.shutdown() + """, + ) + + assert "_DjangoMiddleware" in result.stdout, result.stderr[-2000:] + + def test_a_flush_from_before_send_does_not_repeat(self, posthog_host): + # Each autocapture export runs the hook again. + script = textwrap.dedent( + f""" + import os, time + from posthog.client import Client + + def before_send(sample): + if sample["name"] == "process.cpu.time": + client.flush() + return sample + + client = Client( + "{FAKE_API_KEY}", + host="{posthog_host.url}", + metrics={{ + "autocapture": {{"http": False, "db": False}}, + "flush_interval": 3600, + "before_send": before_send, + }}, + ) + client.flush(timeout_seconds=5) + time.sleep(2) + os._exit(0) + """ + ) + subprocess.run( + [sys.executable, "-c", script], + check=True, + timeout=60, + cwd=Path(__file__).resolve().parents[2], + ) + + assert len(_metrics_exports(posthog_host)) == 1 + + def test_starts_on_a_later_module_setup(self, posthog_host, monkeypatch): + # A Django ready() hook can set the option after setup() already ran. + import posthog + + monkeypatch.setattr(posthog, "default_client", None) + monkeypatch.setattr(posthog, "api_key", FAKE_API_KEY) + monkeypatch.setattr(posthog, "host", posthog_host.url) + monkeypatch.setattr(posthog, "metrics", None) + client = posthog.setup() + try: + assert client._metrics_autocapture is None + monkeypatch.setattr(posthog, "metrics", {"autocapture": True}) + assert posthog.setup() is client + assert client._metrics_autocapture is not None + finally: + client.shutdown() + + def test_module_setup_tries_to_start_once(self, posthog_host, started, monkeypatch): + import posthog + + assert _start(posthog_host, started, InMemoryMetricReader()) is not None + monkeypatch.setattr(posthog, "default_client", None) + monkeypatch.setattr(posthog, "api_key", FAKE_API_KEY) + monkeypatch.setattr(posthog, "host", posthog_host.url) + monkeypatch.setattr(posthog, "metrics", {"autocapture": True}) + with mock.patch("posthog._metrics_autocapture.log") as log: + client = posthog.setup() + posthog.setup() + posthog.setup() + client.shutdown() + + assert log.warning.call_count == 1 diff --git a/pyproject.toml b/pyproject.toml index 0b0b4b51..7e7d1d77 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -48,6 +48,18 @@ otel = [ "opentelemetry-sdk>=1.20.0", "opentelemetry-exporter-otlp-proto-http>=1.20.0", ] +# Metrics autocapture (`metrics={"autocapture": True}`). Library instrumentors +# (requests, Django, psycopg, ...) are separate packages: `opentelemetry-bootstrap +# -a install` adds the ones that match the installed libraries. +# 0.58b0 is the first release with the `process.*` and `cpython.gc.*` names that +# runtime autocapture configures, and 0.60b0 the first in which the httpx +# instrumentor (installed at the same version) excludes URLs. +metrics = [ + "opentelemetry-sdk>=1.39.0", + "opentelemetry-exporter-otlp-proto-http>=1.39.0", + "opentelemetry-instrumentation>=0.60b0", + "opentelemetry-instrumentation-system-metrics>=0.60b0", +] dev = [ "django-stubs", "griffe", @@ -99,6 +111,15 @@ test = [ "fastmcp>=2.0", "opentelemetry-sdk>=1.20.0", "opentelemetry-exporter-otlp-proto-http>=1.20.0", + "opentelemetry-instrumentation>=0.60b0", + "opentelemetry-instrumentation-system-metrics>=0.60b0", + "opentelemetry-instrumentation-requests>=0.60b0", + "opentelemetry-instrumentation-sqlite3>=0.60b0", + "opentelemetry-instrumentation-wsgi>=0.60b0", + "opentelemetry-instrumentation-flask>=0.60b0", + "opentelemetry-instrumentation-httpx>=0.60b0", + "opentelemetry-instrumentation-aiohttp-client>=0.60b0", + "opentelemetry-instrumentation-django>=0.60b0", "pytest-bdd>=8.1.0", "zstandard>=0.23.0", # gevent 25.4.1+ replaces queue.Queue, exercising the compatibility path. diff --git a/uv.lock b/uv.lock index c0f3a3d1..2565e6e5 100644 --- a/uv.lock +++ b/uv.lock @@ -2777,6 +2777,22 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/da/b2/d1413681ff43e13ac9860df27e1226d3199ab0b97b352ceea41abcc660a5/opentelemetry_instrumentation-0.66b1-py3-none-any.whl", hash = "sha256:4c4aa14dc9a24a02325a9d4c42c4d0208dbb1374c2b1b8fe6c9392d59f3e1008", size = 36904, upload-time = "2026-10-06T17:35:11.663Z" }, ] +[[package]] +name = "opentelemetry-instrumentation-aiohttp-client" +version = "0.66b1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "opentelemetry-instrumentation" }, + { name = "opentelemetry-semantic-conventions" }, + { name = "opentelemetry-util-http" }, + { name = "wrapt" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/85/fe/634d77a0109df609eb290d172ab57e45fe35a1ef2c277f3e1908267acaa1/opentelemetry_instrumentation_aiohttp_client-0.66b1.tar.gz", hash = "sha256:6f853dadc53916fd6af41413bcb627b3e36c4a01b581e03b61b2f8eb6031d69f", size = 18849, upload-time = "2026-10-06T17:36:12.072Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7b/30/dd352a722a4b928b22363df249b38b6d173173d36b2eab6d04770fd7d348/opentelemetry_instrumentation_aiohttp_client-0.66b1-py3-none-any.whl", hash = "sha256:bb3d412ae30e83e0692c8e9b600c5990bdab736c7c048847461b4567d504078d", size = 13610, upload-time = "2026-10-06T17:35:13.651Z" }, +] + [[package]] name = "opentelemetry-instrumentation-celery" version = "0.66b1" @@ -2791,6 +2807,129 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/9e/73/b7597b2fede14cafe7122d1028bd6f648d9c1e049a61ab36481a4ed4f0df/opentelemetry_instrumentation_celery-0.66b1-py3-none-any.whl", hash = "sha256:145b5eece41331141edc8f454984238b05b107d7ead928b49e7a1916d5e48e99", size = 13480, upload-time = "2026-10-06T17:35:25.828Z" }, ] +[[package]] +name = "opentelemetry-instrumentation-dbapi" +version = "0.66b1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "opentelemetry-instrumentation" }, + { name = "opentelemetry-semantic-conventions" }, + { name = "wrapt" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/23/b4/35c682a3a5762547cab6ac1d0f6e66a4e549cbbb852862903bb1523036fe/opentelemetry_instrumentation_dbapi-0.66b1.tar.gz", hash = "sha256:715be3080f644de2314d84388a73571acdbeca85bfbc7dc682f214c88cc18022", size = 19990, upload-time = "2026-10-06T17:36:22.281Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/77/89/7f2ccd50975b66b2977a31bb489a2f0205b7fd033fc99046dcfa001c4252/opentelemetry_instrumentation_dbapi-0.66b1-py3-none-any.whl", hash = "sha256:a38043a8a907a321ae6b838f778ad19c8e78c0bfc2f336d6d30c5fdce112e88d", size = 14706, upload-time = "2026-10-06T17:35:28.831Z" }, +] + +[[package]] +name = "opentelemetry-instrumentation-django" +version = "0.66b1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "opentelemetry-instrumentation" }, + { name = "opentelemetry-instrumentation-wsgi" }, + { name = "opentelemetry-semantic-conventions" }, + { name = "opentelemetry-util-http" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e9/88/d0acbd7916aa3040a36486cedb60aa77c129c7029446fa6a00207345695c/opentelemetry_instrumentation_django-0.66b1.tar.gz", hash = "sha256:85b56193b6ce7b8db9c280c3ade4a4991601abc9ada4734c8911971423fd521f", size = 25233, upload-time = "2026-10-06T17:36:22.892Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5b/c3/2739c248b60b2802b9e51d2a3373269f0ca3e16059ea4f1ca7370502118c/opentelemetry_instrumentation_django-0.66b1-py3-none-any.whl", hash = "sha256:23d1052940a025ae1dac70849b998cf9ba66731fa2b7e58254ab7dac5539a989", size = 18914, upload-time = "2026-10-06T17:35:30.052Z" }, +] + +[[package]] +name = "opentelemetry-instrumentation-flask" +version = "0.66b1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "opentelemetry-instrumentation" }, + { name = "opentelemetry-instrumentation-wsgi" }, + { name = "opentelemetry-semantic-conventions" }, + { name = "opentelemetry-util-http" }, + { name = "packaging" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c0/fc/c1a692ceb7ed56818f2f4f88044b5a0895b6666da954dbd566fdf265195b/opentelemetry_instrumentation_flask-0.66b1.tar.gz", hash = "sha256:10f788aff95236f0dbaaa066687283bf6345bb3c77bbb437f0fb67cb6222d5eb", size = 23876, upload-time = "2026-10-06T17:36:25.763Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/52/65/7aa45ca677c51afb185bcd5e7ed121c84b07b2c566a72948a9e56fd16ae7/opentelemetry_instrumentation_flask-0.66b1-py3-none-any.whl", hash = "sha256:2933e71b94cc9e8e943e648d872fd3dead9cdb61295dc6deffd5d6fa8f27a39b", size = 14961, upload-time = "2026-10-06T17:35:34.002Z" }, +] + +[[package]] +name = "opentelemetry-instrumentation-httpx" +version = "0.66b1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "opentelemetry-instrumentation" }, + { name = "opentelemetry-semantic-conventions" }, + { name = "opentelemetry-util-http" }, + { name = "wrapt" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/de/50/41544799b043d14fdfa6fe62fa2518eaba22793fce03e9abde930b92e671/opentelemetry_instrumentation_httpx-0.66b1.tar.gz", hash = "sha256:5865a72c68098c85955a271ab8744b480a36e3ee492d35b8cadb93c7c4dbb618", size = 26637, upload-time = "2026-10-06T17:36:27.265Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4f/c6/e5682b1bfb320b32505e88255c34ae1e99fe9fb220cc465c244e91967eac/opentelemetry_instrumentation_httpx-0.66b1-py3-none-any.whl", hash = "sha256:0342a4002c6dbc6c4bf22cc7e698f50f5c8b77f63325c6f40c94ab87e016bf4d", size = 17723, upload-time = "2026-10-06T17:35:36.501Z" }, +] + +[[package]] +name = "opentelemetry-instrumentation-requests" +version = "0.66b1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "opentelemetry-instrumentation" }, + { name = "opentelemetry-semantic-conventions" }, + { name = "opentelemetry-util-http" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/08/62/dcca0b7a2008675056040c61b70d37b5a24439613c7822068908ad3255ae/opentelemetry_instrumentation_requests-0.66b1.tar.gz", hash = "sha256:28578f72e68e3a5be3226c618ac9570ed360ef1dd22d5d6e0721c6905a4ccc67", size = 17985, upload-time = "2026-10-06T17:36:37.478Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/01/98e9bb7fef12784cd19811e454cf25bce4d774fd3e46b3f5fd2bce237075/opentelemetry_instrumentation_requests-0.66b1-py3-none-any.whl", hash = "sha256:7ba17d984a2bd876b88bf0aafcca27b5e3ff4c1821e0fe07641e380a3cf3a3a2", size = 13320, upload-time = "2026-10-06T17:35:52.477Z" }, +] + +[[package]] +name = "opentelemetry-instrumentation-sqlite3" +version = "0.66b1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "opentelemetry-instrumentation" }, + { name = "opentelemetry-instrumentation-dbapi" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/25/54/742ab70c78a1589c29f5513fd5097845b2c2fda58390bbff0f6e1af2601a/opentelemetry_instrumentation_sqlite3-0.66b1.tar.gz", hash = "sha256:e5d672a347deac385a71fb52d556ea443e2f3cc51eac05cac8f0a739cac0bb38", size = 9019, upload-time = "2026-10-06T17:36:38.74Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3b/6a/c36552844c4e3545da7b2931213256c0717665c7d92246a4fe66e088fd9b/opentelemetry_instrumentation_sqlite3-0.66b1-py3-none-any.whl", hash = "sha256:f282b712f56186c36d592d4ab856d978bc7dd60f4b10500113da8c9eb45ff9bc", size = 8526, upload-time = "2026-10-06T17:35:54.362Z" }, +] + +[[package]] +name = "opentelemetry-instrumentation-system-metrics" +version = "0.66b1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "opentelemetry-instrumentation" }, + { name = "opentelemetry-semantic-conventions" }, + { name = "psutil" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d6/81/ab8cabaf45c5248fa88a0d0d3acea4483c105d76cd6c849654ebdab16f92/opentelemetry_instrumentation_system_metrics-0.66b1.tar.gz", hash = "sha256:b0fccbc1aecf35c182431a035ec4809b8f9607eec692243cf70f23b3bda51231", size = 17207, upload-time = "2026-10-06T17:36:40.518Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a3/fd/ea072c512d65ba4054b02c8b7080cf1b362761d4727ee6c1f36dce53328d/opentelemetry_instrumentation_system_metrics-0.66b1-py3-none-any.whl", hash = "sha256:0654b41eb6db3c2f9ad9979487afcc6f461e5b35df282f0a11d0f465e906d967", size = 13913, upload-time = "2026-10-06T17:35:57.01Z" }, +] + +[[package]] +name = "opentelemetry-instrumentation-wsgi" +version = "0.66b1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "opentelemetry-instrumentation" }, + { name = "opentelemetry-semantic-conventions" }, + { name = "opentelemetry-util-http" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/41/7a/a1ba86d95f849db8f41ffd03c7cb302559ef091d76cff78baddd4ad19205/opentelemetry_instrumentation_wsgi-0.66b1.tar.gz", hash = "sha256:ce803d8e828e75d7225c25a618b692cbd4687a11be64356308ed1feab0f70bab", size = 19527, upload-time = "2026-10-06T17:36:44.181Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a5/4f/17f06b0dc7c542c482b1d3f7f1d0925046f7e156fb39c255634b3c4f526a/opentelemetry_instrumentation_wsgi-0.66b1-py3-none-any.whl", hash = "sha256:8c86390c32fe8d0924b3541f292122d3a2ba6c3accc2bcbcc73ae985ee8e799e", size = 13752, upload-time = "2026-10-06T17:36:03.166Z" }, +] + [[package]] name = "opentelemetry-proto" version = "1.45.1" @@ -2830,6 +2969,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/bc/14/67f8aa798857f8cf686f515bf93d9bb877ce952ddc8efae0fa25b45ce0d6/opentelemetry_semantic_conventions-0.66b1-py3-none-any.whl", hash = "sha256:d4cddeb4315490b35213f55e2bdc9ac54bb1e4d318927475bed62b35545e581b", size = 206279, upload-time = "2026-10-06T17:32:56.103Z" }, ] +[[package]] +name = "opentelemetry-util-http" +version = "0.66b1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7c/b5/df4b61da899f6ebdffdbdf0c8b0f3189ee57151694ccd5b7d50ee2906241/opentelemetry_util_http-0.66b1.tar.gz", hash = "sha256:047dea1a628031f857a5a32261dc0e955bc162d39993ed1cffb8f2cff5ba8a62", size = 11586, upload-time = "2026-10-06T17:36:46.572Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/eb/9b/c77ecaea79ba0de1a11e7f06a7f5eea7043ec23f1860dcf5f03536698e4c/opentelemetry_util_http-0.66b1-py3-none-any.whl", hash = "sha256:8f443d7abcaf29c4a07b373bbd31b5b39132c0ed3c27d015a59dc0323d5b1c58", size = 8387, upload-time = "2026-10-06T17:36:06.984Z" }, +] + [[package]] name = "orjson" version = "3.11.7" @@ -3078,6 +3226,12 @@ dev = [ langchain = [ { name = "langchain" }, ] +metrics = [ + { name = "opentelemetry-exporter-otlp-proto-http" }, + { name = "opentelemetry-instrumentation" }, + { name = "opentelemetry-instrumentation-system-metrics" }, + { name = "opentelemetry-sdk" }, +] otel = [ { name = "opentelemetry-exporter-otlp-proto-http" }, { name = "opentelemetry-sdk" }, @@ -3106,7 +3260,16 @@ test = [ { name = "mcp" }, { name = "openai-agents" }, { name = "opentelemetry-exporter-otlp-proto-http" }, + { name = "opentelemetry-instrumentation" }, + { name = "opentelemetry-instrumentation-aiohttp-client" }, { name = "opentelemetry-instrumentation-celery" }, + { name = "opentelemetry-instrumentation-django" }, + { name = "opentelemetry-instrumentation-flask" }, + { name = "opentelemetry-instrumentation-httpx" }, + { name = "opentelemetry-instrumentation-requests" }, + { name = "opentelemetry-instrumentation-sqlite3" }, + { name = "opentelemetry-instrumentation-system-metrics" }, + { name = "opentelemetry-instrumentation-wsgi" }, { name = "opentelemetry-sdk" }, { name = "parameterized" }, { name = "pydantic" }, @@ -3160,9 +3323,22 @@ requires-dist = [ { name = "mypy", marker = "extra == 'dev'" }, { name = "mypy-baseline", marker = "extra == 'dev'" }, { name = "openai-agents", marker = "extra == 'test'", specifier = ">=0.18" }, + { name = "opentelemetry-exporter-otlp-proto-http", marker = "extra == 'metrics'", specifier = ">=1.39.0" }, { name = "opentelemetry-exporter-otlp-proto-http", marker = "extra == 'otel'", specifier = ">=1.20.0" }, { name = "opentelemetry-exporter-otlp-proto-http", marker = "extra == 'test'", specifier = ">=1.20.0" }, + { name = "opentelemetry-instrumentation", marker = "extra == 'metrics'", specifier = ">=0.60b0" }, + { name = "opentelemetry-instrumentation", marker = "extra == 'test'", specifier = ">=0.60b0" }, + { name = "opentelemetry-instrumentation-aiohttp-client", marker = "extra == 'test'", specifier = ">=0.60b0" }, { name = "opentelemetry-instrumentation-celery", marker = "extra == 'test'", specifier = ">=0.41b0" }, + { name = "opentelemetry-instrumentation-django", marker = "extra == 'test'", specifier = ">=0.60b0" }, + { name = "opentelemetry-instrumentation-flask", marker = "extra == 'test'", specifier = ">=0.60b0" }, + { name = "opentelemetry-instrumentation-httpx", marker = "extra == 'test'", specifier = ">=0.60b0" }, + { name = "opentelemetry-instrumentation-requests", marker = "extra == 'test'", specifier = ">=0.60b0" }, + { name = "opentelemetry-instrumentation-sqlite3", marker = "extra == 'test'", specifier = ">=0.60b0" }, + { name = "opentelemetry-instrumentation-system-metrics", marker = "extra == 'metrics'", specifier = ">=0.60b0" }, + { name = "opentelemetry-instrumentation-system-metrics", marker = "extra == 'test'", specifier = ">=0.60b0" }, + { name = "opentelemetry-instrumentation-wsgi", marker = "extra == 'test'", specifier = ">=0.60b0" }, + { name = "opentelemetry-sdk", marker = "extra == 'metrics'", specifier = ">=1.39.0" }, { name = "opentelemetry-sdk", marker = "extra == 'otel'", specifier = ">=1.20.0" }, { name = "opentelemetry-sdk", marker = "extra == 'test'", specifier = ">=1.20.0" }, { name = "packaging", marker = "extra == 'dev'" }, @@ -3190,7 +3366,7 @@ requires-dist = [ { name = "zstandard", marker = "extra == 'test'", specifier = ">=0.23.0" }, { name = "zstandard", marker = "extra == 'zstd'", specifier = ">=0.23.0" }, ] -provides-extras = ["langchain", "async", "zstd", "otel", "dev", "test"] +provides-extras = ["langchain", "async", "zstd", "otel", "metrics", "dev", "test"] [package.metadata.requires-dev] dev = [{ name = "claude-agent-sdk", specifier = ">=0.1.50" }] @@ -3327,6 +3503,34 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c4/72/02445137af02769918a93807b2b7890047c32bfb9f90371cbc12688819eb/protobuf-6.33.6-py3-none-any.whl", hash = "sha256:77179e006c476e69bf8e8ce866640091ec42e1beb80b213c3900006ecfba6901", size = 170656, upload-time = "2026-03-18T19:04:59.826Z" }, ] +[[package]] +name = "psutil" +version = "7.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/aa/c6/d1ddf4abb55e93cebc4f2ed8b5d6dbad109ecb8d63748dd2b20ab5e57ebe/psutil-7.2.2.tar.gz", hash = "sha256:0746f5f8d406af344fd547f1c8daa5f5c33dbc293bb8d6a16d80b4bb88f59372", size = 493740, upload-time = "2026-01-28T18:14:54.428Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/08/510cbdb69c25a96f4ae523f733cdc963ae654904e8db864c07585ef99875/psutil-7.2.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2edccc433cbfa046b980b0df0171cd25bcaeb3a68fe9022db0979e7aa74a826b", size = 130595, upload-time = "2026-01-28T18:14:57.293Z" }, + { url = "https://files.pythonhosted.org/packages/d6/f5/97baea3fe7a5a9af7436301f85490905379b1c6f2dd51fe3ecf24b4c5fbf/psutil-7.2.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e78c8603dcd9a04c7364f1a3e670cea95d51ee865e4efb3556a3a63adef958ea", size = 131082, upload-time = "2026-01-28T18:14:59.732Z" }, + { url = "https://files.pythonhosted.org/packages/37/d6/246513fbf9fa174af531f28412297dd05241d97a75911ac8febefa1a53c6/psutil-7.2.2-cp313-cp313t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1a571f2330c966c62aeda00dd24620425d4b0cc86881c89861fbc04549e5dc63", size = 181476, upload-time = "2026-01-28T18:15:01.884Z" }, + { url = "https://files.pythonhosted.org/packages/b8/b5/9182c9af3836cca61696dabe4fd1304e17bc56cb62f17439e1154f225dd3/psutil-7.2.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:917e891983ca3c1887b4ef36447b1e0873e70c933afc831c6b6da078ba474312", size = 184062, upload-time = "2026-01-28T18:15:04.436Z" }, + { url = "https://files.pythonhosted.org/packages/16/ba/0756dca669f5a9300d0cbcbfae9a4c30e446dfc7440ffe43ded5724bfd93/psutil-7.2.2-cp313-cp313t-win_amd64.whl", hash = "sha256:ab486563df44c17f5173621c7b198955bd6b613fb87c71c161f827d3fb149a9b", size = 139893, upload-time = "2026-01-28T18:15:06.378Z" }, + { url = "https://files.pythonhosted.org/packages/1c/61/8fa0e26f33623b49949346de05ec1ddaad02ed8ba64af45f40a147dbfa97/psutil-7.2.2-cp313-cp313t-win_arm64.whl", hash = "sha256:ae0aefdd8796a7737eccea863f80f81e468a1e4cf14d926bd9b6f5f2d5f90ca9", size = 135589, upload-time = "2026-01-28T18:15:08.03Z" }, + { url = "https://files.pythonhosted.org/packages/81/69/ef179ab5ca24f32acc1dac0c247fd6a13b501fd5534dbae0e05a1c48b66d/psutil-7.2.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:eed63d3b4d62449571547b60578c5b2c4bcccc5387148db46e0c2313dad0ee00", size = 130664, upload-time = "2026-01-28T18:15:09.469Z" }, + { url = "https://files.pythonhosted.org/packages/7b/64/665248b557a236d3fa9efc378d60d95ef56dd0a490c2cd37dafc7660d4a9/psutil-7.2.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7b6d09433a10592ce39b13d7be5a54fbac1d1228ed29abc880fb23df7cb694c9", size = 131087, upload-time = "2026-01-28T18:15:11.724Z" }, + { url = "https://files.pythonhosted.org/packages/d5/2e/e6782744700d6759ebce3043dcfa661fb61e2fb752b91cdeae9af12c2178/psutil-7.2.2-cp314-cp314t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fa4ecf83bcdf6e6c8f4449aff98eefb5d0604bf88cb883d7da3d8d2d909546a", size = 182383, upload-time = "2026-01-28T18:15:13.445Z" }, + { url = "https://files.pythonhosted.org/packages/57/49/0a41cefd10cb7505cdc04dab3eacf24c0c2cb158a998b8c7b1d27ee2c1f5/psutil-7.2.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e452c464a02e7dc7822a05d25db4cde564444a67e58539a00f929c51eddda0cf", size = 185210, upload-time = "2026-01-28T18:15:16.002Z" }, + { url = "https://files.pythonhosted.org/packages/dd/2c/ff9bfb544f283ba5f83ba725a3c5fec6d6b10b8f27ac1dc641c473dc390d/psutil-7.2.2-cp314-cp314t-win_amd64.whl", hash = "sha256:c7663d4e37f13e884d13994247449e9f8f574bc4655d509c3b95e9ec9e2b9dc1", size = 141228, upload-time = "2026-01-28T18:15:18.385Z" }, + { url = "https://files.pythonhosted.org/packages/f2/fc/f8d9c31db14fcec13748d373e668bc3bed94d9077dbc17fb0eebc073233c/psutil-7.2.2-cp314-cp314t-win_arm64.whl", hash = "sha256:11fe5a4f613759764e79c65cf11ebdf26e33d6dd34336f8a337aa2996d71c841", size = 136284, upload-time = "2026-01-28T18:15:19.912Z" }, + { url = "https://files.pythonhosted.org/packages/e7/36/5ee6e05c9bd427237b11b3937ad82bb8ad2752d72c6969314590dd0c2f6e/psutil-7.2.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ed0cace939114f62738d808fdcecd4c869222507e266e574799e9c0faa17d486", size = 129090, upload-time = "2026-01-28T18:15:22.168Z" }, + { url = "https://files.pythonhosted.org/packages/80/c4/f5af4c1ca8c1eeb2e92ccca14ce8effdeec651d5ab6053c589b074eda6e1/psutil-7.2.2-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:1a7b04c10f32cc88ab39cbf606e117fd74721c831c98a27dc04578deb0c16979", size = 129859, upload-time = "2026-01-28T18:15:23.795Z" }, + { url = "https://files.pythonhosted.org/packages/b5/70/5d8df3b09e25bce090399cf48e452d25c935ab72dad19406c77f4e828045/psutil-7.2.2-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:076a2d2f923fd4821644f5ba89f059523da90dc9014e85f8e45a5774ca5bc6f9", size = 155560, upload-time = "2026-01-28T18:15:25.976Z" }, + { url = "https://files.pythonhosted.org/packages/63/65/37648c0c158dc222aba51c089eb3bdfa238e621674dc42d48706e639204f/psutil-7.2.2-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b0726cecd84f9474419d67252add4ac0cd9811b04d61123054b9fb6f57df6e9e", size = 156997, upload-time = "2026-01-28T18:15:27.794Z" }, + { url = "https://files.pythonhosted.org/packages/8e/13/125093eadae863ce03c6ffdbae9929430d116a246ef69866dad94da3bfbc/psutil-7.2.2-cp36-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:fd04ef36b4a6d599bbdb225dd1d3f51e00105f6d48a28f006da7f9822f2606d8", size = 148972, upload-time = "2026-01-28T18:15:29.342Z" }, + { url = "https://files.pythonhosted.org/packages/04/78/0acd37ca84ce3ddffaa92ef0f571e073faa6d8ff1f0559ab1272188ea2be/psutil-7.2.2-cp36-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b58fabe35e80b264a4e3bb23e6b96f9e45a3df7fb7eed419ac0e5947c61e47cc", size = 148266, upload-time = "2026-01-28T18:15:31.597Z" }, + { url = "https://files.pythonhosted.org/packages/b4/90/e2159492b5426be0c1fef7acba807a03511f97c5f86b3caeda6ad92351a7/psutil-7.2.2-cp37-abi3-win_amd64.whl", hash = "sha256:eb7e81434c8d223ec4a219b5fc1c47d0417b12be7ea866e24fb5ad6e84b3d988", size = 137737, upload-time = "2026-01-28T18:15:33.849Z" }, + { url = "https://files.pythonhosted.org/packages/8c/c7/7bb2e321574b10df20cbde462a94e2b71d05f9bbda251ef27d104668306a/psutil-7.2.2-cp37-abi3-win_arm64.whl", hash = "sha256:8c233660f575a5a89e6d4cb65d9f938126312bca76d8fe087b947b3a1aaac9ee", size = 134617, upload-time = "2026-01-28T18:15:36.514Z" }, +] + [[package]] name = "py-key-value-aio" version = "0.4.5" From e345e42b342ab6269ac00a62abe9cd5e8bb8053c Mon Sep 17 00:00:00 2001 From: "releaser-posthog-python[bot]" <262414804+releaser-posthog-python[bot]@users.noreply.github.com> Date: Thu, 29 Oct 2026 10:29:55 +0000 Subject: [PATCH 39/60] chore: Release v7.80.0 [skip ci] (cherry picked from commit ce03e476bb0256bed4f3b6745ebb50fc3dead745) --- .sampo/changesets/metrics-autocapture.md | 13 ------------- openfeature-provider/CHANGELOG.md | 6 ++++++ openfeature-provider/pyproject.toml | 2 +- posthog/CHANGELOG.md | 15 +++++++++++++++ posthog/version.py | 2 +- pyproject.toml | 2 +- uv.lock | 4 ++-- 7 files changed, 26 insertions(+), 18 deletions(-) delete mode 100644 .sampo/changesets/metrics-autocapture.md diff --git a/.sampo/changesets/metrics-autocapture.md b/.sampo/changesets/metrics-autocapture.md deleted file mode 100644 index d562f886..00000000 --- a/.sampo/changesets/metrics-autocapture.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -pypi/posthog: minor ---- - -Add `metrics={"autocapture": True}` (alpha): HTTP, database and runtime metrics from the official OpenTelemetry instrumentations, with no instrumentation code. Install `posthog[metrics]` and run `opentelemetry-bootstrap -a install` to add the instrumentors for your libraries. Create the client before your web app (Django: in `AppConfig.ready()`): apps created earlier are not measured. For Flask, use module lookup as below, or import the `Flask` class only after the client starts; an earlier `from flask import Flask` alias is not instrumented. Already-served Flask apps are not re-instrumented after shutdown; create a new app for a replacement client. Autocapture does not start when the app already set up OpenTelemetry; export those metrics to PostHog instead. Raw URLs and paths, client addresses, user agents and captured headers are never recorded. `metrics.before_send` also applies to autocaptured measurements: it can change their value and attributes, or drop them. - -```python -import flask -from posthog import Posthog - -client = Posthog("phc_...", metrics={"autocapture": True}) -app = flask.Flask(__name__) -``` diff --git a/openfeature-provider/CHANGELOG.md b/openfeature-provider/CHANGELOG.md index b06b9903..5abeea9b 100644 --- a/openfeature-provider/CHANGELOG.md +++ b/openfeature-provider/CHANGELOG.md @@ -4,6 +4,12 @@ All notable changes to `openfeature-provider-posthog` are documented here. This file is maintained by [Sampo](https://github.com/bruits/sampo) from changesets in `.sampo/changesets/` that target `pypi/openfeature-provider-posthog`. +## 0.1.131 — 2026-10-29 + +### Patch changes + +- Updated dependencies: posthog@7.80.0 + ## 0.1.130 — 2026-10-28 ### Patch changes diff --git a/openfeature-provider/pyproject.toml b/openfeature-provider/pyproject.toml index 2d23f2d0..b7a496b4 100644 --- a/openfeature-provider/pyproject.toml +++ b/openfeature-provider/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "openfeature-provider-posthog" -version = "0.1.130" +version = "0.1.131" description = "Official PostHog provider for the OpenFeature Python SDK." readme = "README.md" authors = [{ name = "PostHog", email = "engineering@posthog.com" }] diff --git a/posthog/CHANGELOG.md b/posthog/CHANGELOG.md index 063d59e0..08fe1166 100644 --- a/posthog/CHANGELOG.md +++ b/posthog/CHANGELOG.md @@ -1,5 +1,20 @@ # posthog +## 7.80.0 — 2026-10-29 + +### Minor changes + +- [febb3ed](https://github.com/posthog/posthog-python/commit/febb3ed2f1584e68c05c73625becbdd18fe5f944) Add `metrics={"autocapture": True}` (alpha): HTTP, database and runtime metrics from the official OpenTelemetry instrumentations, with no instrumentation code. Install `posthog[metrics]` and run `opentelemetry-bootstrap -a install` to add the instrumentors for your libraries. Create the client before your web app (Django: in `AppConfig.ready()`): apps created earlier are not measured. For Flask, use module lookup as below, or import the `Flask` class only after the client starts; an earlier `from flask import Flask` alias is not instrumented. Already-served Flask apps are not re-instrumented after shutdown; create a new app for a replacement client. Autocapture does not start when the app already set up OpenTelemetry; export those metrics to PostHog instead. Raw URLs and paths, client addresses, user agents and captured headers are never recorded. `metrics.before_send` also applies to autocaptured measurements: it can change their value and attributes, or drop them. + + ```python + import flask + from posthog import Posthog + + client = Posthog("phc_...", metrics={"autocapture": True}) + app = flask.Flask(__name__) + ``` + — Thanks @DanielVisca! + ## 7.79.0 — 2026-10-28 ### Minor changes diff --git a/posthog/version.py b/posthog/version.py index c6c3571e..d9cc0934 100644 --- a/posthog/version.py +++ b/posthog/version.py @@ -1 +1 @@ -VERSION = "7.79.0" +VERSION = "7.80.0" diff --git a/pyproject.toml b/pyproject.toml index 7e7d1d77..18c7119c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "posthog" -version = "7.79.0" +version = "7.80.0" description = "Integrate PostHog into any python application." authors = [{ name = "PostHog", email = "engineering@posthog.com" }] maintainers = [{ name = "PostHog", email = "engineering@posthog.com" }] diff --git a/uv.lock b/uv.lock index 2565e6e5..c3d02bed 100644 --- a/uv.lock +++ b/uv.lock @@ -2656,7 +2656,7 @@ wheels = [ [[package]] name = "openfeature-provider-posthog" -version = "0.1.130" +version = "0.1.131" source = { editable = "openfeature-provider" } dependencies = [ { name = "openfeature-sdk" }, @@ -3193,7 +3193,7 @@ wheels = [ [[package]] name = "posthog" -version = "7.79.0" +version = "7.80.0" source = { editable = "." } dependencies = [ { name = "distro" }, From 412719defb0f2274af9071f0874119598f2f3abe Mon Sep 17 00:00:00 2001 From: "releaser-posthog-python[bot]" <262414804+releaser-posthog-python[bot]@users.noreply.github.com> Date: Thu, 29 Oct 2026 10:30:02 +0000 Subject: [PATCH 40/60] Update generated references (cherry picked from commit 175fac6d6e4fdbb179ead60869ed5a864ea1f6ff) --- .../posthog-python-references-7.80.0.json | 3548 +++++++++++++++++ .../posthog-python-references-latest.json | 4 +- 2 files changed, 3550 insertions(+), 2 deletions(-) create mode 100644 references/posthog-python-references-7.80.0.json diff --git a/references/posthog-python-references-7.80.0.json b/references/posthog-python-references-7.80.0.json new file mode 100644 index 00000000..cecc1969 --- /dev/null +++ b/references/posthog-python-references-7.80.0.json @@ -0,0 +1,3548 @@ +{ + "id": "posthog-python", + "hogRef": "0.3", + "info": { + "version": "7.80.0", + "id": "posthog-python", + "title": "PostHog Python SDK", + "description": "Integrate PostHog into any python application.", + "slugPrefix": "posthog-python", + "specUrl": "https://github.com/PostHog/posthog-python" + }, + "types": [ + { + "id": "FeatureFlag", + "name": "FeatureFlag", + "path": "posthog.types.FeatureFlag", + "properties": [ + { + "name": "key", + "type": "str", + "description": "Field: key" + }, + { + "name": "enabled", + "type": "bool", + "description": "Field: enabled" + }, + { + "name": "variant", + "type": "Optional[str]", + "description": "Field: variant" + }, + { + "name": "reason", + "type": "Optional[FlagReason]", + "description": "Field: reason" + }, + { + "name": "metadata", + "type": "Union[FlagMetadata, LegacyFlagMetadata]", + "description": "Field: metadata" + } + ], + "example": "" + }, + { + "id": "FeatureFlagResult", + "name": "FeatureFlagResult", + "path": "posthog.types.FeatureFlagResult", + "properties": [ + { + "name": "key", + "type": "str", + "description": "Field: key" + }, + { + "name": "enabled", + "type": "bool", + "description": "Field: enabled" + }, + { + "name": "variant", + "type": "Optional[str]", + "description": "Field: variant" + }, + { + "name": "payload", + "type": "Optional[Any]", + "description": "Field: payload" + }, + { + "name": "reason", + "type": "Optional[str]", + "description": "Field: reason" + } + ], + "example": "" + }, + { + "id": "FlagMetadata", + "name": "FlagMetadata", + "path": "posthog.types.FlagMetadata", + "properties": [ + { + "name": "id", + "type": "int", + "description": "Field: id" + }, + { + "name": "payload", + "type": "Optional[str]", + "description": "Field: payload" + }, + { + "name": "version", + "type": "int", + "description": "Field: version" + }, + { + "name": "description", + "type": "str", + "description": "Field: description" + }, + { + "name": "has_experiment", + "type": "Optional[bool]", + "description": "Field: has_experiment" + } + ], + "example": "" + }, + { + "id": "FlagReason", + "name": "FlagReason", + "path": "posthog.types.FlagReason", + "properties": [ + { + "name": "code", + "type": "str", + "description": "Field: code" + }, + { + "name": "condition_index", + "type": "Optional[int]", + "description": "Field: condition_index" + }, + { + "name": "description", + "type": "str", + "description": "Field: description" + } + ], + "example": "" + }, + { + "id": "FlagsAndPayloads", + "name": "FlagsAndPayloads", + "path": "posthog.types.FlagsAndPayloads", + "properties": [ + { + "name": "featureFlags", + "type": "Optional[dict[str, Union[bool, str]]]", + "description": "Field: featureFlags" + }, + { + "name": "featureFlagPayloads", + "type": "Optional[dict[str, Any]]", + "description": "Field: featureFlagPayloads" + } + ], + "example": "" + }, + { + "id": "FlagsResponse", + "name": "FlagsResponse", + "path": "posthog.types.FlagsResponse", + "properties": [ + { + "name": "flags", + "type": "dict[str, FeatureFlag]", + "description": "Field: flags" + }, + { + "name": "errorsWhileComputingFlags", + "type": "bool", + "description": "Field: errorsWhileComputingFlags" + }, + { + "name": "requestId", + "type": "str", + "description": "Field: requestId" + }, + { + "name": "quotaLimit", + "type": "Optional[list[str]]", + "description": "Field: quotaLimit" + }, + { + "name": "evaluatedAt", + "type": "Optional[int]", + "description": "Field: evaluatedAt" + }, + { + "name": "minimalFlagCalledEvents", + "type": "bool", + "description": "Field: minimalFlagCalledEvents" + } + ], + "example": "" + }, + { + "id": "LegacyFlagMetadata", + "name": "LegacyFlagMetadata", + "path": "posthog.types.LegacyFlagMetadata", + "properties": [ + { + "name": "payload", + "type": "Any", + "description": "Field: payload" + } + ], + "example": "" + }, + { + "id": "SendFeatureFlagsOptions", + "name": "SendFeatureFlagsOptions", + "path": "posthog.types.SendFeatureFlagsOptions", + "properties": [ + { + "name": "should_send", + "type": "bool", + "description": "Field: should_send" + }, + { + "name": "only_evaluate_locally", + "type": "Optional[bool]", + "description": "Field: only_evaluate_locally" + }, + { + "name": "person_properties", + "type": "Optional[dict[str, Any]]", + "description": "Field: person_properties" + }, + { + "name": "group_properties", + "type": "Optional[dict[str, dict[str, Any]]]", + "description": "Field: group_properties" + }, + { + "name": "flag_keys_filter", + "type": "Optional[list[str]]", + "description": "Field: flag_keys_filter" + } + ], + "example": "" + }, + { + "id": "OptionalCaptureArgs", + "name": "OptionalCaptureArgs", + "path": "posthog.args.OptionalCaptureArgs", + "properties": [ + { + "name": "distinct_id", + "type": "NotRequired[Union[Number, str, UUID, int, any]]", + "description": "Field: distinct_id" + }, + { + "name": "properties", + "type": "NotRequired[Optional[dict[str, Any]]]", + "description": "Field: properties" + }, + { + "name": "timestamp", + "type": "NotRequired[Union[datetime, str, any]]", + "description": "Field: timestamp" + }, + { + "name": "uuid", + "type": "NotRequired[Union[str, UUID, any]]", + "description": "Field: uuid" + }, + { + "name": "groups", + "type": "NotRequired[Optional[dict[str, str]]]", + "description": "Field: groups" + }, + { + "name": "flags", + "type": "NotRequired[Optional[ForwardRef('FeatureFlagEvaluations')]]", + "description": "Field: flags" + }, + { + "name": "send_feature_flags", + "type": "NotRequired[Union[bool, SendFeatureFlagsOptions, any]]", + "description": "Field: send_feature_flags" + }, + { + "name": "disable_geoip", + "type": "NotRequired[Optional[bool]]", + "description": "Field: disable_geoip" + }, + { + "name": "_property_allowlist", + "type": "NotRequired[Optional[frozenset[str]]]", + "description": "Field: _property_allowlist" + } + ], + "example": "" + }, + { + "id": "OptionalSetArgs", + "name": "OptionalSetArgs", + "path": "posthog.args.OptionalSetArgs", + "properties": [ + { + "name": "distinct_id", + "type": "NotRequired[Union[Number, str, UUID, int, any]]", + "description": "Field: distinct_id" + }, + { + "name": "properties", + "type": "NotRequired[Optional[dict[str, Any]]]", + "description": "Field: properties" + }, + { + "name": "timestamp", + "type": "NotRequired[Union[datetime, str, any]]", + "description": "Field: timestamp" + }, + { + "name": "uuid", + "type": "NotRequired[Union[str, UUID, any]]", + "description": "Field: uuid" + }, + { + "name": "disable_geoip", + "type": "NotRequired[Optional[bool]]", + "description": "Field: disable_geoip" + } + ], + "example": "" + }, + { + "id": "SendFeatureFlagsOptions", + "name": "SendFeatureFlagsOptions", + "path": "posthog.types.SendFeatureFlagsOptions", + "properties": [ + { + "name": "should_send", + "type": "bool", + "description": "Field: should_send" + }, + { + "name": "only_evaluate_locally", + "type": "Optional[bool]", + "description": "Field: only_evaluate_locally" + }, + { + "name": "person_properties", + "type": "Optional[dict[str, Any]]", + "description": "Field: person_properties" + }, + { + "name": "group_properties", + "type": "Optional[dict[str, dict[str, Any]]]", + "description": "Field: group_properties" + }, + { + "name": "flag_keys_filter", + "type": "Optional[list[str]]", + "description": "Field: flag_keys_filter" + } + ], + "example": "" + } + ], + "classes": [ + { + "id": "PostHog", + "title": "PostHog", + "description": "This is the SDK reference for the PostHog Python SDK. You can learn more about example usage in the [Python SDK documentation](/docs/libraries/python). You can also follow [Flask](/docs/libraries/flask) and [Django](/docs/libraries/django) guides to integrate PostHog into your project. For long-running applications, create one client during application startup and reuse it for the lifetime of the process. This keeps background queues predictable and makes shutdown flushing straightforward. Multiple clients are still supported for intentional multi-project or multi-host setups.", + "functions": [ + { + "id": "__init__", + "title": "Client", + "description": "Initialize a new PostHog client instance.", + "details": "", + "category": "Initialization", + "params": [ + { + "name": "project_api_key", + "description": "PostHog project API key/token.", + "isOptional": true, + "type": "str" + }, + { + "name": "host", + "description": "PostHog host. Defaults to the US ingestion endpoint when not set. App hosts such as ``https://us.posthog.com`` are mapped to the corresponding ingestion host.", + "isOptional": false, + "type": "any" + }, + { + "name": "debug", + "description": "Enable verbose SDK logging and re-raise errors from public API methods.", + "isOptional": false, + "type": "bool" + }, + { + "name": "max_queue_size", + "description": "Maximum number of events buffered before upload.", + "isOptional": false, + "type": "int" + }, + { + "name": "send", + "description": "If False, queueing succeeds but events are not sent.", + "isOptional": false, + "type": "bool" + }, + { + "name": "on_error", + "description": "Optional callback invoked by background consumers when an upload fails. Keep it short and non-blocking. Calling lifecycle methods directly is safe and deferred, but do not start another thread or task that calls ``flush()``, ``join()``, or ``shutdown()`` and then wait for it from the callback.", + "isOptional": false, + "type": "any" + }, + { + "name": "flush_at", + "description": "Number of queued events that triggers a batch upload.", + "isOptional": false, + "type": "int" + }, + { + "name": "flush_interval", + "description": "Maximum seconds a background consumer waits before flushing a partial batch.", + "isOptional": false, + "type": "float" + }, + { + "name": "gzip", + "description": "Whether to gzip event upload payloads.", + "isOptional": false, + "type": "bool" + }, + { + "name": "max_retries", + "description": "Number of upload retries. Values below 0 are treated as 0.", + "isOptional": false, + "type": "int" + }, + { + "name": "sync_mode", + "description": "If True, send each event synchronously instead of using background worker threads. This blocks the calling thread; in asyncio applications such as FastAPI, use ``AsyncPosthog`` instead.", + "isOptional": false, + "type": "bool" + }, + { + "name": "timeout", + "description": "HTTP request timeout in seconds for event uploads.", + "isOptional": false, + "type": "float" + }, + { + "name": "thread", + "description": "Number of background consumer threads.", + "isOptional": false, + "type": "int" + }, + { + "name": "poll_interval", + "description": "Seconds between local feature flag definition refreshes.", + "isOptional": false, + "type": "int" + }, + { + "name": "personal_api_key", + "description": "Deprecated alias for ``secret_key``. Still honored for backwards compatibility; prefer ``secret_key``, which also accepts a Project Secret API Key.", + "isOptional": false, + "type": "any" + }, + { + "name": "disabled", + "description": "If True, disable captures and API requests. Useful in tests.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable server-side GeoIP enrichment. Defaults to True.", + "isOptional": false, + "type": "bool" + }, + { + "name": "is_server", + "description": "Whether events are emitted from a server-side runtime. Defaults to True; set to False when using the SDK as a client/CLI so the device OS is attributed to the person normally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "historical_migration", + "description": "Mark events as historical migration imports.", + "isOptional": false, + "type": "bool" + }, + { + "name": "feature_flags_request_timeout_seconds", + "description": "Timeout in seconds for feature flag and remote config requests.", + "isOptional": false, + "type": "float" + }, + { + "name": "feature_flags_request_max_retries", + "description": "Number of retries for feature flag requests after network, transport, or timeout failures. Defaults to 1. Set to 0 to disable retries.", + "isOptional": false, + "type": "int" + }, + { + "name": "super_properties", + "description": "Properties merged into every captured event.", + "isOptional": false, + "type": "any" + }, + { + "name": "enable_exception_autocapture", + "description": "Automatically capture uncaught exceptions.", + "isOptional": false, + "type": "bool" + }, + { + "name": "log_captured_exceptions", + "description": "Also log exceptions captured by error tracking.", + "isOptional": false, + "type": "bool" + }, + { + "name": "project_root", + "description": "Root path used to determine in-app stack frames for captured exceptions. Defaults to the current working directory.", + "isOptional": false, + "type": "any" + }, + { + "name": "privacy_mode", + "description": "For AI observability, capture usage metadata without prompt inputs or outputs.", + "isOptional": false, + "type": "bool" + }, + { + "name": "before_send", + "description": "Optional callback that can modify or drop events before upload. Return ``None`` to drop an event.", + "isOptional": false, + "type": "any" + }, + { + "name": "flag_fallback_cache_url", + "description": "Optional feature flag fallback cache URL, such as ``memory://local/?ttl=300&size=10000`` or a Redis URL.", + "isOptional": false, + "type": "any" + }, + { + "name": "enable_local_evaluation", + "description": "Whether to poll feature flag definitions for local evaluation when a personal API key is configured.", + "isOptional": false, + "type": "bool" + }, + { + "name": "flag_definition_cache_provider", + "description": "Optional external cache provider for sharing feature flag definitions across workers.", + "isOptional": true, + "type": "FlagDefinitionCacheProvider" + }, + { + "name": "capture_exception_code_variables", + "description": "Capture local variable values on exception stack frames.", + "isOptional": false, + "type": "bool" + }, + { + "name": "code_variables_mask_patterns", + "description": "Variable-name patterns to mask when capturing code variables.", + "isOptional": false, + "type": "any" + }, + { + "name": "code_variables_ignore_patterns", + "description": "Variable-name patterns to omit when capturing code variables.", + "isOptional": false, + "type": "any" + }, + { + "name": "code_variables_mask_url_credentials", + "description": "Scrub credentials embedded in URLs/DSNs (e.g. ``user:pass@host``) from captured code variables, regardless of the surrounding variable name. Defaults to True.", + "isOptional": false, + "type": "any" + }, + { + "name": "code_variables_detect_secrets", + "description": "Last-resort entropy-based detection that redacts high-entropy secret-looking values (API keys, tokens, strong passwords) sitting in innocuously-named variables, after the name and URL checks. Skips structured ids (UUIDs, ObjectIds, hashes). Defaults to True.", + "isOptional": false, + "type": "any" + }, + { + "name": "in_app_modules", + "description": "Module/package prefixes treated as in-app frames in captured exceptions.", + "isOptional": false, + "type": "UnionType[list[str], any]" + }, + { + "name": "enable_exception_autocapture_rate_limiting", + "description": "Rate limit autocaptured exceptions client-side with a token bucket per exception type. Disabled by default.", + "isOptional": false, + "type": "bool" + }, + { + "name": "exception_autocapture_bucket_size", + "description": "Maximum burst of autocaptured exceptions allowed per exception type (token bucket size, clamped to 0-100).", + "isOptional": false, + "type": "int" + }, + { + "name": "exception_autocapture_refill_rate", + "description": "Tokens restored per refill interval for each exception type's bucket.", + "isOptional": false, + "type": "int" + }, + { + "name": "exception_autocapture_refill_interval_seconds", + "description": "Seconds between token refills for autocaptured exception rate limiting.", + "isOptional": false, + "type": "int" + }, + { + "name": "capture_mode", + "description": "Capture wire protocol to use. Defaults to ``CaptureMode.V0`` (legacy ``/batch/``). Set ``CaptureMode.V1`` (or pass the string ``\"v1\"``) to opt into ``/i/v1/analytics/events``. When omitted, the ``POSTHOG_CAPTURE_MODE`` env var is consulted, then ``V0``.", + "isOptional": false, + "type": "CaptureMode" + }, + { + "name": "capture_compression", + "description": "Request-body compression for capture-v1 uploads (ignored in V0, which uses ``gzip``). ``CaptureCompression.GZIP`` or ``DEFLATE`` (or the strings ``\"gzip\"``/``\"deflate\"``). When omitted, the ``POSTHOG_CAPTURE_COMPRESSION`` env var is consulted, then the legacy ``gzip`` flag, then no compression.", + "isOptional": false, + "type": "CaptureCompression" + }, + { + "name": "secret_key", + "description": "A Personal API Key or Project Secret API Key, used to authenticate local feature flag evaluation, remote config payloads, and decrypted flag payloads. Example:: posthog.Client(project_api_key, secret_key=\"phx_...\")", + "isOptional": false, + "type": "any" + }, + { + "name": "metrics", + "description": "", + "isOptional": true, + "type": "dict" + }, + { + "name": "enable_full_ai_capture", + "description": "Route PostHog AI wrapper events through the dedicated AI capture endpoint and capture full AI content: skips string truncation and passes media (base64/data URIs) through unredacted. ``privacy_mode`` always wins. Defaults to False.", + "isOptional": false, + "type": "bool" + }, + { + "name": "capture_trace_context", + "description": "When OpenTelemetry is installed and a valid span is active at capture time, add its trace and span IDs as ``$trace_id`` and ``$span_id`` properties to events captured with ``capture()`` and ``capture_ai()``, so they can be correlated with backend traces. Explicit ``$trace_id``/``$span_id`` values passed in ``properties`` win. Exception events (``capture_exception``) always attach these IDs regardless of this setting. Defaults to False.", + "isOptional": false, + "type": "bool" + }, + { + "name": "_use_ai_lane", + "description": "", + "isOptional": false, + "type": "bool" + }, + { + "name": "_enable_multimodal_capture", + "description": "", + "isOptional": false, + "type": "bool" + }, + { + "name": "traces", + "description": "Config dict for distributed tracing: ``service_name``, ``service_version``, ``environment``, ``resource_attributes``, ``flush_interval`` (5 s), ``max_queue_size`` (2048), ``max_export_batch_size`` (512), ``max_live_spans`` (10000), ``max_span_age`` (3600 s), ``max_attributes_per_span`` (128), ``max_events_per_span`` (128), ``max_attribute_value_length`` (8192). ``before_span_send`` is a callable, or a list run in order, that receives each finished span as a dict (``trace_id``, ``span_id`` and ``parent_span_id`` are read-only) and returns it, edited, or ``None`` to drop it; a hook that raises drops the span. Tracing is off until this is provided. Spans export on a background timer even with ``sync_mode``; serverless handlers should call ``flush()`` before returning. Defaults to None.", + "isOptional": true, + "type": "dict" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import Posthog\n\nposthog = Posthog('', host='')" + } + ] + }, + { + "id": "alias", + "title": "alias", + "description": "Create an alias between two distinct IDs.", + "details": "", + "category": "Identification", + "params": [ + { + "name": "previous_id", + "description": "The previous distinct ID. Required - the call is dropped with a warning if it is missing or empty.", + "isOptional": true, + "type": "Number" + }, + { + "name": "distinct_id", + "description": "The new distinct ID to alias to. Falls back to the context distinct ID; the call is dropped with a warning if neither is available.", + "isOptional": true, + "type": "str" + }, + { + "name": "timestamp", + "description": "The timestamp of the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC.", + "isOptional": false, + "type": "datetime" + }, + { + "name": "uuid", + "description": "A unique identifier for the event. If provided, it must be a valid UUID string or uuid.UUID instance; invalid values are ignored and replaced with a newly generated UUID.", + "isOptional": true, + "type": "str" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this event.", + "isOptional": true, + "type": "bool" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.alias(previous_id='distinct_id', distinct_id='alias_id')" + } + ] + }, + { + "id": "capture", + "title": "capture", + "description": "Captures an event manually. [Learn about capture best practices](https://posthog.com/docs/product-analytics/capture-events)", + "details": "", + "category": "Capture", + "params": [ + { + "name": "event", + "description": "The event name to capture.", + "isOptional": true, + "type": "str" + }, + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalCaptureArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Anonymous event", + "code": "# Anonymous event\nposthog.capture('some-anon-event')" + }, + { + "id": "example_2", + "name": "Context usage", + "code": "# Context usage\nfrom posthog import identify_context, new_context\nwith new_context():\n identify_context('distinct_id_of_the_user')\n posthog.capture('user_signed_up')\n posthog.capture('user_logged_in')\n posthog.capture('some-custom-action', distinct_id='distinct_id_of_the_user')" + }, + { + "id": "example_3", + "name": "Set event properties", + "code": "# Set event properties\nposthog.capture(\n \"user_signed_up\",\n distinct_id=\"distinct_id_of_the_user\",\n properties={\n \"login_type\": \"email\",\n \"is_free_trial\": \"true\"\n }\n)" + }, + { + "id": "example_4", + "name": "Page view event", + "code": "# Page view event\nposthog.capture('$pageview', distinct_id=\"distinct_id_of_the_user\", properties={'$current_url': 'https://example.com'})" + } + ] + }, + { + "id": "capture_ai", + "title": "capture_ai", + "description": "Capture an AI event on the dedicated AI capture endpoint. Beta: the signature is stable; operational limits (per-event size cap, batching, endpoint) may change without notice. Takes the same arguments and returns the same value as `capture()`: the event UUID, or None when the event was not admitted (disabled client, or dropped by `before_send`). The event is queued on an isolated AI lane with its own consumer pool and a higher per-event size cap, posting to the dedicated AI ingestion endpoint. The payload is sent as given \u2014 no redaction or truncation is applied here.", + "details": "", + "category": "Capture", + "params": [ + { + "name": "event", + "description": "", + "isOptional": true, + "type": "str" + }, + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalCaptureArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + } + }, + { + "id": "capture_exception", + "title": "capture_exception", + "description": "Capture an exception for error tracking. When OpenTelemetry is installed and a valid span is active, its trace and span IDs are added as ``$trace_id`` and ``$span_id`` event properties.", + "details": "", + "category": "Error Tracking", + "params": [ + { + "name": "exception", + "description": "The exception to capture.", + "isOptional": true, + "type": "BaseException" + }, + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalCaptureArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "try:\n # Some code that might fail\n pass\nexcept Exception as e:\n posthog.capture_exception(e, 'user_distinct_id', properties=additional_properties)" + } + ] + }, + { + "id": "evaluate_flags", + "title": "evaluate_flags", + "description": "Evaluate all feature flags for a user in a single call and return a :class:`FeatureFlagEvaluations` snapshot. Branch on ``.is_enabled()`` / ``.get_flag()`` and pass the same snapshot to :meth:`capture` via the ``flags`` option so events carry the exact flag values the code branched on. Prefer this over repeated ``get_feature_flag()`` calls and over ``capture(send_feature_flags=True)`` \u2014 it consolidates flag evaluation into a single ``/flags`` request per incoming request. Local evaluation is transparent: when the poller resolves a flag, the snapshot's ``$feature_flag_called`` events are tagged ``locally_evaluated=True`` and reason ``\"Evaluated locally\"``.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The user's distinct ID. If ``None``, falls back to the context distinct_id. If still unresolvable, returns an empty snapshot.", + "isOptional": false, + "type": "Number" + }, + { + "name": "groups", + "description": "Mapping of group type to group key.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "Person properties to use for evaluation.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "Group properties keyed by group type.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "If True, never fall back to remote evaluation \u2014 flags that can't be evaluated locally are omitted from the snapshot and listed with a reason in its ``unresolved_flags``.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup.", + "isOptional": true, + "type": "bool" + }, + { + "name": "flag_keys", + "description": "Optional list that scopes local evaluation, the underlying ``/flags`` request, and the returned snapshot. When omitted or ``None``, all flags are evaluated. An empty list returns an empty snapshot without evaluating flags. A requested key absent from loaded local definitions is included in one remote fallback per ``evaluate_flags`` call unless ``only_evaluate_locally`` is True. If the server also does not know the key, it is omitted from the snapshot.", + "isOptional": true, + "type": "list[str]" + }, + { + "name": "device_id", + "description": "Optional device ID override. If not provided, falls back to the context device_id (which may be set via tracing headers). Used by experience-continuity flags to match users across distinct_id changes.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "FeatureFlagEvaluations" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "flags = posthog.evaluate_flags(\n \"user_123\",\n person_properties={\"plan\": \"enterprise\"},\n)\nif flags.is_enabled(\"new-dashboard\"):\n render_new_dashboard()\nposthog.capture(\"page_viewed\", distinct_id=\"user_123\", flags=flags)" + } + ] + }, + { + "id": "feature_enabled", + "title": "feature_enabled", + "description": "Check if a feature flag is enabled for a user.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "The feature flag key.", + "isOptional": true, + "type": "str" + }, + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to only evaluate locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "send_feature_flag_events", + "description": "Whether to send feature flag events.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[bool]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "is_my_flag_enabled = posthog.feature_enabled('flag-key', 'distinct_id_of_your_user')\nif is_my_flag_enabled:\n # Do something differently for this user\n # Optional: fetch the payload\n matched_flag_payload = posthog.get_feature_flag_payload('flag-key', 'distinct_id_of_your_user')" + } + ] + }, + { + "id": "feature_flag_definitions", + "title": "feature_flag_definitions", + "description": "Return feature flag definitions loaded for local evaluation. Returns: The currently loaded feature flag definitions, or ``None`` before local evaluation has loaded definitions.", + "details": "", + "category": "Feature flags", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "flush", + "title": "flush", + "description": "Attempt immediate delivery of queued events, ended spans and autocaptured metrics, keeping the client reusable. For a client shared across requests or serverless invocations (including the module-level client), call ``flush()`` before a short-lived invocation returns. Use ``shutdown()`` instead only for final process cleanup or when disposing of a client that will not be reused. Flush does not stop the background workers or feature flag poller, and does not end open spans. Returning does not guarantee server receipt: pending work can remain after a timeout or span export failure, and failed events may be dropped by the consumers. Calls made directly from SDK callbacks such as ``on_error`` are deferred rather than blocking the worker that invoked the callback.", + "details": "", + "category": null, + "params": [ + { + "name": "timeout_seconds", + "description": "Seconds to wait for event queues to drain, shared across event lanes. Defaults to 10 seconds. Pass ``None`` to wait without a time limit; this does not guarantee successful delivery. Queued spans flush concurrently. The span exporter waits up to this many seconds for an in-flight flush, then uses a fresh drain budget once it acquires the lock. If the lock wait times out, the in-flight flush is left to finish. At least one span request is attempted after acquiring the lock, even with no budget left; retriable failures are retried after backoff while budget remains, with one last attempt at the deadline. Each span request is bounded by the client's ``timeout`` in seconds, and the last request is not cut short. This is therefore not a strict wall-clock limit for the whole call. With ``None``, a retriable span failure is left for a later flush rather than retried within this call.", + "isOptional": true, + "type": "float" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.capture('event_name', distinct_id='user_id')\n# Before a serverless invocation returns; keep the shared client open.\nposthog.flush(timeout_seconds=10)" + } + ] + }, + { + "id": "get_active_span", + "title": "get_active_span", + "description": "The span that is active in the current context, or ``None``. Alpha. Only entering a span (``with posthog.start_span(...) as span:``) makes it active; a span started manually is not. Use it to propagate the trace to the next service: ``span.traceparent()`` is the header value.", + "details": "", + "category": "Tracing", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[Span]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "span = posthog.get_active_span()\nif span is not None:\n headers[\"traceparent\"] = span.traceparent()" + } + ] + }, + { + "id": "get_all_flags", + "title": "get_all_flags", + "description": "Get all feature flags for a user.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to only evaluate locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "flag_keys_to_evaluate", + "description": "A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance.", + "isOptional": true, + "type": "list[str]" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[dict[str, Union[bool, str]]]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.get_all_flags('distinct_id_of_your_user')" + } + ] + }, + { + "id": "get_all_flags_and_payloads", + "title": "get_all_flags_and_payloads", + "description": "Get all feature flags and their payloads for a user.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to only evaluate locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "flag_keys_to_evaluate", + "description": "A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance.", + "isOptional": true, + "type": "list[str]" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "FlagsAndPayloads" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.get_all_flags_and_payloads('distinct_id_of_your_user')" + } + ] + }, + { + "id": "get_feature_flag", + "title": "get_feature_flag", + "description": "Get multivariate feature flag value for a user.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "The feature flag key.", + "isOptional": true, + "type": "str" + }, + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to only evaluate locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "send_feature_flag_events", + "description": "Whether to send feature flag events.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Union[bool, str, any]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "enabled_variant = posthog.get_feature_flag('flag-key', 'distinct_id_of_your_user')\nif enabled_variant == 'variant-key': # replace 'variant-key' with the key of your variant\n # Do something differently for this user\n # Optional: fetch the payload\n matched_flag_payload = posthog.get_feature_flag_payload('flag-key', 'distinct_id_of_your_user')" + } + ] + }, + { + "id": "get_feature_flag_evaluation_runtime", + "title": "get_feature_flag_evaluation_runtime", + "description": "Return where a locally loaded feature flag is meant to be evaluated.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "The feature flag key.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[FeatureFlagEvaluationRuntime]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import FeatureFlagEvaluationRuntime\n\nruntime = posthog.get_feature_flag_evaluation_runtime(\"my-flag\")\nif runtime is FeatureFlagEvaluationRuntime.SERVER:\n ..." + } + ] + }, + { + "id": "get_feature_flag_keys_by_evaluation_runtime", + "title": "get_feature_flag_keys_by_evaluation_runtime", + "description": "Return the keys of locally loaded flags that a runtime can evaluate. A flag set to ``FeatureFlagEvaluationRuntime.ALL`` suits either runtime, so it is returned for ``CLIENT`` and for ``SERVER``, and asking for ``ALL`` returns every loaded flag. Use this to decide which flags to hand to a browser when a backend serves flags to its own frontend.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "evaluation_runtime", + "description": "The runtime to match, as a ``FeatureFlagEvaluationRuntime`` or its string value.", + "isOptional": true, + "type": "FeatureFlagEvaluationRuntime" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "list[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import FeatureFlagEvaluationRuntime\n\nclient_keys = posthog.get_feature_flag_keys_by_evaluation_runtime(\n FeatureFlagEvaluationRuntime.CLIENT\n)" + } + ] + }, + { + "id": "get_feature_flag_payload", + "title": "get_feature_flag_payload", + "description": "Get the payload for a feature flag.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "The feature flag key.", + "isOptional": true, + "type": "str" + }, + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "match_value", + "description": "The specific flag value to get payload for.", + "isOptional": false, + "type": "bool" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to only evaluate locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "send_feature_flag_events", + "description": "Deprecated. Use get_feature_flag() instead if you need events.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[object]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "is_my_flag_enabled = posthog.feature_enabled('flag-key', 'distinct_id_of_your_user')\n\nif is_my_flag_enabled:\n # Do something differently for this user\n # Optional: fetch the payload\n matched_flag_payload = posthog.get_feature_flag_payload('flag-key', 'distinct_id_of_your_user')" + } + ] + }, + { + "id": "get_feature_flag_result", + "title": "get_feature_flag_result", + "description": "Get a FeatureFlagResult object which contains the flag result and payload for a key by evaluating locally or remotely depending on whether local evaluation is enabled and the flag can be locally evaluated. This also captures the `$feature_flag_called` event unless `send_feature_flag_events` is `False`.", + "details": "", + "category": null, + "params": [ + { + "name": "key", + "description": "The feature flag key.", + "isOptional": true, + "type": "str" + }, + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to only evaluate locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "send_feature_flag_events", + "description": "Whether to send feature flag events.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[FeatureFlagResult]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "flag_result = posthog.get_feature_flag_result('flag-key', 'distinct_id_of_your_user')\nif flag_result and flag_result.get_value() == 'variant-key':\n # Do something differently for this user\n # Optional: fetch the payload\n matched_flag_payload = flag_result.payload" + } + ] + }, + { + "id": "get_feature_flags_and_payloads", + "title": "get_feature_flags_and_payloads", + "description": "Get feature flags and payloads for a user.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "flag_keys_to_evaluate", + "description": "A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance.", + "isOptional": true, + "type": "list[str]" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "FlagsAndPayloads" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "result = posthog.get_feature_flags_and_payloads('')" + } + ] + }, + { + "id": "get_feature_payloads", + "title": "get_feature_payloads", + "description": "Get feature flag payloads for a user, preserving valid serialized JSON.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "flag_keys_to_evaluate", + "description": "A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance.", + "isOptional": true, + "type": "list[str]" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "dict[str, Optional[str]]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "payloads = posthog.get_feature_payloads('')" + } + ] + }, + { + "id": "get_feature_variants", + "title": "get_feature_variants", + "description": "Get feature flag variants for a user.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "flag_keys_to_evaluate", + "description": "A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance.", + "isOptional": true, + "type": "list[str]" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "dict[str, Union[bool, str]]" + } + }, + { + "id": "get_flags_decision", + "title": "get_flags_decision", + "description": "Get feature flags decision.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": false, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "flag_keys_to_evaluate", + "description": "A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance.", + "isOptional": true, + "type": "list[str]" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "FlagsResponse" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "decision = posthog.get_flags_decision('user123')" + } + ] + }, + { + "id": "get_remote_config_payload", + "title": "get_remote_config_payload", + "description": "Get the payload for a remote config feature flag.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "The remote config feature flag key.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "get_tags", + "title": "get_tags", + "description": "Get all tags from the current context. Returns: Dict of all tags in the current context.", + "details": "", + "category": "Contexts", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "dict[str, Any]" + } + }, + { + "id": "group_identify", + "title": "group_identify", + "description": "Identify a group and set its properties.", + "details": "", + "category": "Identification", + "params": [ + { + "name": "group_type", + "description": "The type of group (e.g., 'company', 'team'). Required - the call is dropped with a warning if it is missing or empty.", + "isOptional": true, + "type": "str" + }, + { + "name": "group_key", + "description": "The unique identifier for the group. Required - the call is dropped with a warning if it is missing or empty.", + "isOptional": true, + "type": "str" + }, + { + "name": "properties", + "description": "A dictionary of properties to set on the group.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "timestamp", + "description": "The timestamp of the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC.", + "isOptional": false, + "type": "datetime" + }, + { + "name": "uuid", + "description": "A unique identifier for the event. If provided, it must be a valid UUID string or uuid.UUID instance; invalid values are ignored and replaced with a newly generated UUID.", + "isOptional": false, + "type": "str" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this event.", + "isOptional": true, + "type": "bool" + }, + { + "name": "distinct_id", + "description": "The distinct ID of the user performing the action.", + "isOptional": false, + "type": "Number" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.group_identify('company', 'company_id_in_your_db', {\n 'name': 'Awesome Inc.',\n 'employees': 11\n})" + } + ] + }, + { + "id": "identify_context", + "title": "identify_context", + "description": "Identify the current context with a distinct ID.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "distinct_id", + "description": "The distinct ID to associate with the current context and its children.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + } + }, + { + "id": "join", + "title": "join", + "description": "Attempt to process queued events and end the consumer threads. Do not use directly, call `shutdown()` instead. Failed or undrainable events may be dropped and reported through logging or ``on_error``; returning does not guarantee server receipt. Lifecycle cleanup is attempted once, and cleanup failures are logged without retry.", + "details": "", + "category": null, + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.join()" + } + ] + }, + { + "id": "load_feature_flags", + "title": "load_feature_flags", + "description": "Load feature flags for local evaluation.", + "details": "", + "category": "Feature flags", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.load_feature_flags()" + } + ] + }, + { + "id": "new_context", + "title": "new_context", + "description": "Create a new context for managing shared state. Learn more about [contexts](/docs/libraries/python#contexts).", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "fresh", + "description": "Whether to create a fresh context that doesn't inherit from parent.", + "isOptional": false, + "type": "bool" + }, + { + "name": "capture_exceptions", + "description": "Whether to automatically capture exceptions in this context. If omitted, defaults to this client's exception autocapture setting.", + "isOptional": true, + "type": "bool" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "with client.new_context():\n client.identify_context('')\n client.capture('event_name')" + } + ] + }, + { + "id": "scoped", + "title": "scoped", + "description": "Decorator that creates a new context for the wrapped function using this client.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "fresh", + "description": "Whether to create a fresh context that doesn't inherit from parent.", + "isOptional": false, + "type": "bool" + }, + { + "name": "capture_exceptions", + "description": "Whether to automatically capture exceptions in this context. If omitted, defaults to this client's exception autocapture setting.", + "isOptional": true, + "type": "bool" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "set", + "title": "set", + "description": "Set properties on a person profile.", + "details": "", + "category": "Identification", + "params": [ + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalSetArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Set with distinct id", + "code": "# Set with distinct id\nposthog.set(distinct_id='user123', properties={'name': 'Max Hedgehog'})" + } + ] + }, + { + "id": "set_context_device_id", + "title": "set_context_device_id", + "description": "Set the device ID for the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "device_id", + "description": "The device ID to associate with the current context and its children.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + } + }, + { + "id": "set_context_session", + "title": "set_context_session", + "description": "Set the session ID for the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "session_id", + "description": "The session ID to associate with the current context and its children.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + } + }, + { + "id": "set_once", + "title": "set_once", + "description": "Set properties on a person profile only if they haven't been set before.", + "details": "", + "category": "Identification", + "params": [ + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalSetArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.set_once(distinct_id='user123', properties={'initial_signup_date': '2024-01-01'})" + } + ] + }, + { + "id": "shutdown", + "title": "shutdown", + "description": "Attempt final delivery and permanently shut down the client. Call this for final process cleanup or when disposing of a client that will not be reused. It closes capture lanes, stops background workers and the feature flag poller, and tears down integrations. Do not call it at the end of each request or serverless invocation when the client is shared; call ``flush()`` before returning instead. A shut-down client cannot be reused; create a new client if needed. Normally this method blocks until queued events have been attempted and cleanup finishes. Failed or undrainable events may be dropped and reported through logging or ``on_error``; returning does not guarantee server receipt. Event draining has no timeout. Queued spans get one final flush with up to 30 seconds waiting for an in-flight flush, then a fresh 30-second drain budget; the last HTTP request is not cut short. Any spans it cannot send are discarded with a warning, as are spans still open. Lifecycle cleanup is attempted once, and cleanup failures are logged without retry. When called directly from an SDK callback such as ``on_error``, shutdown is deferred to avoid blocking the worker that invoked the callback. If the callback must coordinate a blocking shutdown, have it signal an application-owned thread and return before that thread calls shutdown. Do not wait inside the callback for another thread or task that calls a lifecycle method.", + "details": "", + "category": null, + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.shutdown()" + } + ] + }, + { + "id": "start_span", + "title": "start_span", + "description": "Start a span for distributed tracing. Alpha. Returns a span handle. Use it as a context manager to make it the active span for the block and end it on exit (recording a raised exception on the way out); or call ``end()`` yourself for a span that cannot wrap a block. Spans started inside the block nest under it automatically. Always returns a usable handle, even when tracing is off, so calling code never branches.", + "details": "", + "category": "Tracing", + "params": [ + { + "name": "name", + "description": "A low-cardinality operation name, e.g. ``GET /users/:id``. Variable values belong in attributes, not the name.", + "isOptional": true, + "type": "str" + }, + { + "name": "kind", + "description": "``internal`` (default), ``server``, ``client``, ``producer`` or ``consumer``.", + "isOptional": true, + "type": "str" + }, + { + "name": "attributes", + "description": "Initial attributes.", + "isOptional": true, + "type": "Mapping[str, Any]" + }, + { + "name": "parent", + "description": "A span handle, or an inbound W3C ``traceparent`` header value to continue a remote trace. Defaults to the active span. A forked child starts with no active span; pass the parent span to continue a trace across a fork.", + "isOptional": false, + "type": "Span" + }, + { + "name": "tracestate", + "description": "The inbound ``tracestate`` header accompanying a ``traceparent`` string ``parent``; preserved and propagated.", + "isOptional": true, + "type": "str" + }, + { + "name": "start_time", + "description": "A ``datetime`` or epoch seconds, to backdate the span.", + "isOptional": false, + "type": "datetime" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Span" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog = Posthog(\"\", traces={\"service_name\": \"checkout-api\"})\n\nwith posthog.start_span(\"POST /checkout\", parent=request.headers.get(\"traceparent\")) as span:\n span.set_attribute(\"plan\", user.plan)\n with posthog.start_span(\"db.query\", kind=\"client\"):\n ...\n outgoing_headers = {\"traceparent\": span.traceparent()}" + } + ] + }, + { + "id": "tag", + "title": "tag", + "description": "Add a tag to the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "name", + "description": "The tag key.", + "isOptional": true, + "type": "str" + }, + { + "name": "value", + "description": "The tag value.", + "isOptional": true, + "type": "Any" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + } + } + ] + }, + { + "id": "PostHogModule", + "title": "PostHog Module Functions", + "description": "Global functions available in the PostHog module", + "functions": [ + { + "id": "alias", + "title": "alias", + "description": "Associate user behaviour before and after they e.g. register, login, or perform some other identifying action.", + "details": "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?\". Particularly useful for associating user behaviour before and after they e.g. register, login, or perform some other identifying action.", + "category": "Identification", + "params": [ + { + "name": "previous_id", + "description": "The unique ID of the user before", + "isOptional": true, + "type": "Number" + }, + { + "name": "distinct_id", + "description": "The current unique id", + "isOptional": true, + "type": "str" + }, + { + "name": "timestamp", + "description": "Optional timestamp for the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC.", + "isOptional": false, + "type": "datetime" + }, + { + "name": "uuid", + "description": "Optional UUID for the event", + "isOptional": true, + "type": "str" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup", + "isOptional": true, + "type": "bool" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Alias user", + "code": "# Alias user\nfrom posthog import alias\nalias(previous_id='distinct_id', distinct_id='alias_id')" + } + ] + }, + { + "id": "capture", + "title": "capture", + "description": "Capture anything a user does within your system.", + "details": "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. A capture call requires an event name to specify the event. We recommend using [verb] [noun], like `movie played` or `movie updated` to easily identify what your events mean later on. Capture takes a number of optional arguments, which are defined by the `OptionalCaptureArgs` type.", + "category": "Events", + "params": [ + { + "name": "event", + "description": "The event name to specify the event **kwargs: Optional arguments including:", + "isOptional": true, + "type": "str" + }, + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalCaptureArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Context and capture usage", + "code": "# Context and capture usage\nfrom posthog import new_context, identify_context, tag_context, capture\n# Enter a new context (e.g. a request/response cycle, an instance of a background job, etc)\nwith new_context():\n # Associate this context with some user, by distinct_id\n identify_context('some user')\n\n # Capture an event, associated with the context-level distinct ID ('some user')\n capture('movie started')\n\n # Capture an event associated with some other user (overriding the context-level distinct ID)\n capture('movie joined', distinct_id='some-other-user')\n\n # Capture an event with some properties\n capture('movie played', properties={'movie_id': '123', 'category': 'romcom'})\n\n # Capture an event with some properties\n capture('purchase', properties={'product_id': '123', 'category': 'romcom'})\n # Capture an event with some associated group\n capture('purchase', groups={'company': 'id:5'})\n\n # Adding a tag to the current context will cause it to appear on all subsequent events\n tag_context('some-tag', 'some-value')\n\n capture('another-event') # Will be captured with `'some-tag': 'some-value'` in the properties dict" + }, + { + "id": "example_2", + "name": "Set event properties", + "code": "# Set event properties\nfrom posthog import capture\ncapture(\n \"user_signed_up\",\n distinct_id=\"distinct_id_of_the_user\",\n properties={\n \"login_type\": \"email\",\n \"is_free_trial\": \"true\"\n }\n)" + } + ] + }, + { + "id": "capture_ai", + "title": "capture_ai", + "description": "Capture an AI event on the dedicated AI capture endpoint. Beta: the signature is stable; operational limits (per-event size cap, batching, endpoint) may change without notice. Takes the same arguments and returns the same value as `capture()`: the event UUID, or None when the event was not admitted (disabled client, or dropped by `before_send`). The event is delivered on an isolated queue with its own consumer pool and a higher per-event size cap, posting to the dedicated AI ingestion endpoint. The payload is sent as given \u2014 no redaction or truncation is applied here.", + "details": "", + "category": "Events", + "params": [ + { + "name": "event", + "description": "The event name, normally one of the `$ai_*` event names. **kwargs: Same optional arguments as `capture()`.", + "isOptional": true, + "type": "str" + }, + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalCaptureArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import capture_ai\n\nuuid = capture_ai(\n \"$ai_generation\",\n distinct_id=\"user_123\",\n properties={\"$ai_model\": \"gpt-5\"},\n)" + } + ] + }, + { + "id": "capture_exception", + "title": "capture_exception", + "description": "Capture exceptions that happen in your code.", + "details": "Capture exception is idempotent - if it is called twice with the same exception instance, only a occurrence will be tracked in posthog. This is because, generally, contexts will cause exceptions to be captured automatically. However, to ensure you track an exception, if you catch and do not re-raise it, capturing it manually is recommended, unless you are certain it will have crossed a context boundary (e.g. by existing a `with posthog.new_context():` block already). If the passed exception was raised and caught, the captured stack trace will consist of every frame between where the exception was raised and the point at which it is captured (the \"traceback\"). If the passed exception was never raised, e.g. if you call `posthog.capture_exception(ValueError(\"Some Error\"))`, the stack trace captured will be the full stack trace at the moment the exception was captured. Note that heavy use of contexts will lead to truncated stack traces, as the exception will be captured by the context entered most recently, 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. `capture_exception` takes the same set of optional arguments as `capture`.", + "category": "Events", + "params": [ + { + "name": "exception", + "description": "The exception to capture. If not provided, the current exception is captured via `sys.exc_info()` **kwargs: Optional capture arguments including distinct_id, properties, timestamp, uuid, groups, flags, send_feature_flags, and disable_geoip. Overriding reserved exception properties through ``properties`` is deprecated and will stop working in the next major version.", + "isOptional": false, + "type": "BaseException" + }, + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalCaptureArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Capture exception", + "code": "# Capture exception\nfrom posthog import capture_exception\ntry:\n risky_operation()\nexcept Exception as e:\n capture_exception(e)" + } + ] + }, + { + "id": "evaluate_flags", + "title": "evaluate_flags", + "description": "Evaluate all feature flags for a user in a single call and return a :class:`FeatureFlagEvaluations` snapshot. Branch on ``.is_enabled()`` / ``.get_flag()`` and pass the same snapshot to ``capture()`` via the ``flags`` option so events carry the exact flag values the code branched on. Prefer this over repeated ``get_feature_flag()`` calls and over ``capture(send_feature_flags=True)`` \u2014 it consolidates flag evaluation into a single ``/flags`` request per incoming request.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The user's distinct ID. If ``None``, falls back to the context distinct_id. If still unresolvable, returns an empty snapshot.", + "isOptional": false, + "type": "Number" + }, + { + "name": "groups", + "description": "Mapping of group type to group key.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "Person properties to use for evaluation.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "Group properties keyed by group type.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "If ``True``, never fall back to remote evaluation and omit flags that cannot be evaluated locally. The snapshot's ``unresolved_flags`` lists them with a reason.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup.", + "isOptional": true, + "type": "bool" + }, + { + "name": "flag_keys", + "description": "Optional list that scopes local evaluation, the underlying ``/flags`` request, and the returned snapshot. When omitted or ``None``, all flags are evaluated. An empty list returns an empty snapshot without evaluating flags. A requested key absent from loaded local definitions is included in one remote fallback per ``evaluate_flags`` call unless ``only_evaluate_locally`` is ``True``. If the server also does not know the key, it is omitted from the snapshot.", + "isOptional": true, + "type": "list[str]" + }, + { + "name": "device_id", + "description": "Optional device ID override. If not provided, falls back to the context device_id (which may be set via tracing headers). Used by experience-continuity flags to match users across distinct_id changes.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "FeatureFlagEvaluations" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import evaluate_flags, capture\nflags = evaluate_flags(\"user_123\", person_properties={\"plan\": \"enterprise\"})\nif flags.is_enabled(\"new-dashboard\"):\n render_new_dashboard()\ncapture(\"page_viewed\", distinct_id=\"user_123\", flags=flags)" + } + ] + }, + { + "id": "feature_enabled", + "title": "feature_enabled", + "description": "Use feature flags to enable or disable features for users.", + "details": "You can call `posthog.load_feature_flags()` before to make sure you're not doing unexpected requests.", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "The feature flag key", + "isOptional": true, + "type": "str" + }, + { + "name": "distinct_id", + "description": "The user's distinct ID", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "Groups mapping", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "Person properties", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "Group properties", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to evaluate only locally", + "isOptional": false, + "type": "bool" + }, + { + "name": "send_feature_flag_events", + "description": "Whether to send feature flag events", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "Optional device ID override for experience-continuity flags", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[bool]" + }, + "examples": [ + { + "id": "example_1", + "name": "Boolean feature flag", + "code": "# Boolean feature flag\nfrom posthog import feature_enabled, get_feature_flag_payload\nis_my_flag_enabled = feature_enabled('flag-key', 'distinct_id_of_your_user')\nif is_my_flag_enabled:\n matched_flag_payload = get_feature_flag_payload('flag-key', 'distinct_id_of_your_user')" + } + ] + }, + { + "id": "feature_flag_definitions", + "title": "feature_flag_definitions", + "description": "Returns loaded feature flags.", + "details": "Returns loaded feature flags, if any. Helpful for debugging what flag information you have loaded.", + "category": "Feature flags", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import feature_flag_definitions\ndefinitions = feature_flag_definitions()" + } + ] + }, + { + "id": "flush", + "title": "flush", + "description": "Attempt immediate delivery of queued events and ended spans, keeping the global client reusable. Call ``flush()`` before a short-lived request or serverless invocation returns when using the shared module-level client. Use ``shutdown()`` only for final process cleanup or a client that will not be reused. Flush does not stop the background workers or feature flag poller, and does not end open spans. Returning does not guarantee server receipt: pending work can remain after a timeout or span export failure, and failed events may be dropped by consumers. Calls made directly from SDK callbacks such as ``on_error`` are deferred rather than blocking the worker that invoked the callback.", + "details": "", + "category": "Client management", + "params": [ + { + "name": "timeout_seconds", + "description": "Seconds to wait for event queues to drain, shared across event lanes. Defaults to 10 seconds. Pass ``None`` to wait without a time limit; this does not guarantee successful delivery. Queued spans flush concurrently, waiting up to this many seconds for an in-flight flush before starting a fresh drain budget. A lock wait timeout leaves the in-flight flush to finish. At least one span request is attempted after acquiring the lock, even with no budget left; retriable failures are retried after backoff while budget remains, with one last attempt at the deadline. The last span request is not cut short and is bounded by the client's ``timeout`` in seconds, so this is not a strict wall-clock limit for the whole call. With ``None``, a retriable span failure is left for a later flush rather than retried within this call.", + "isOptional": true, + "type": "float" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "import posthog\nposthog.capture('event_name', distinct_id='user_id')\n# Before a serverless invocation returns; keep the global client open.\nposthog.flush(timeout_seconds=10)" + } + ] + }, + { + "id": "get_active_span", + "title": "get_active_span", + "description": "The span that is active in the current context, or ``None``. Alpha. Only entering a span (``with posthog.start_span(...) as span:``) makes it active; a span started manually is not. Use it to propagate the trace to the next service: ``span.traceparent()`` is the header value.", + "details": "", + "category": "Tracing", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[Span]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "span = posthog.get_active_span()\nif span is not None:\n headers[\"traceparent\"] = span.traceparent()" + } + ] + }, + { + "id": "get_all_flags", + "title": "get_all_flags", + "description": "Get all flags for a given user.", + "details": "Flags are key-value pairs where the key is the flag key and the value is the flag variant, or True, or False.", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The user's distinct ID", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "Groups mapping", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "Person properties", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "Group properties", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to evaluate only locally", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "Optional device ID override for experience-continuity flags", + "isOptional": true, + "type": "str" + }, + { + "name": "flag_keys_to_evaluate", + "description": "Optional list of flag keys to evaluate (evaluates all if None)", + "isOptional": true, + "type": "list[str]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[dict[str, Union[bool, str]]]" + }, + "examples": [ + { + "id": "example_1", + "name": "All flags for user", + "code": "# All flags for user\nfrom posthog import get_all_flags\nget_all_flags('distinct_id_of_your_user')" + } + ] + }, + { + "id": "get_all_flags_and_payloads", + "title": "get_all_flags_and_payloads", + "description": "Get all feature flag values and payloads for a user.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The user's distinct ID.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "Mapping of group type to group key.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "Person properties to use for evaluation.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "Group properties keyed by group type.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to evaluate only locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup.", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "Optional device ID override for experience-continuity flags.", + "isOptional": true, + "type": "str" + }, + { + "name": "flag_keys_to_evaluate", + "description": "Optional list of flag keys to evaluate. Evaluates all flags when omitted.", + "isOptional": true, + "type": "list[str]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "FlagsAndPayloads" + } + }, + { + "id": "get_feature_flag", + "title": "get_feature_flag", + "description": "Get feature flag variant for users. Used with experiments.", + "details": "`groups` are a mapping from group type to group key. So, if you have a group type of \"organization\" and a group key of \"5\", you would pass groups={\"organization\": \"5\"}. `group_properties` take the format: { group_type_name: { group_properties } }. So, for example, if you have the group type \"organization\" and the group key \"5\", with the properties name, and employee count, you'll send these as: group_properties={\"organization\": {\"name\": \"PostHog\", \"employees\": 11}}.", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "The feature flag key", + "isOptional": true, + "type": "str" + }, + { + "name": "distinct_id", + "description": "The user's distinct ID", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "Groups mapping from group type to group key", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "Person properties", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "Group properties in format { group_type_name: { group_properties } }", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to evaluate only locally", + "isOptional": false, + "type": "bool" + }, + { + "name": "send_feature_flag_events", + "description": "Whether to send feature flag events", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "Optional device ID override for experience-continuity flags", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Union[bool, str, any]" + }, + "examples": [ + { + "id": "example_1", + "name": "Multivariate feature flag", + "code": "# Multivariate feature flag\nfrom posthog import get_feature_flag, get_feature_flag_payload\nenabled_variant = get_feature_flag('flag-key', 'distinct_id_of_your_user')\nif enabled_variant == 'variant-key':\n matched_flag_payload = get_feature_flag_payload('flag-key', 'distinct_id_of_your_user')" + } + ] + }, + { + "id": "get_feature_flag_evaluation_runtime", + "title": "get_feature_flag_evaluation_runtime", + "description": "Return where a locally loaded feature flag is meant to be evaluated.", + "details": "Reads the `evaluation_runtime` each flag definition carries, so no extra request is made. Returns `None` when local evaluation has not loaded a definition for this key. A definition that carries no runtime reports `FeatureFlagEvaluationRuntime.ALL`, the default PostHog applies.", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[FeatureFlagEvaluationRuntime]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import FeatureFlagEvaluationRuntime, get_feature_flag_evaluation_runtime\nruntime = get_feature_flag_evaluation_runtime(\"my-flag\")" + } + ] + }, + { + "id": "get_feature_flag_keys_by_evaluation_runtime", + "title": "get_feature_flag_keys_by_evaluation_runtime", + "description": "Return the keys of locally loaded flags that a runtime can evaluate.", + "details": "A flag set to `FeatureFlagEvaluationRuntime.ALL` suits either runtime, so it is returned for `CLIENT` and for `SERVER`. Use this to decide which flags to hand to a browser when a backend serves flags to its own frontend.", + "category": "Feature flags", + "params": [ + { + "name": "evaluation_runtime", + "description": "", + "isOptional": true, + "type": "FeatureFlagEvaluationRuntime" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "list[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import FeatureFlagEvaluationRuntime, get_feature_flag_keys_by_evaluation_runtime\nclient_keys = get_feature_flag_keys_by_evaluation_runtime(FeatureFlagEvaluationRuntime.CLIENT)" + } + ] + }, + { + "id": "get_feature_flag_payload", + "title": "get_feature_flag_payload", + "description": "Get the payload associated with a feature flag value. Deprecated for new code. Prefer ``evaluate_flags()`` and ``flags.get_flag_payload(key)`` so flag evaluation happens once per request.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "The feature flag key.", + "isOptional": true, + "type": "str" + }, + { + "name": "distinct_id", + "description": "The user's distinct ID.", + "isOptional": true, + "type": "Number" + }, + { + "name": "match_value", + "description": "Optional flag value to use when selecting a payload.", + "isOptional": false, + "type": "bool" + }, + { + "name": "groups", + "description": "Mapping of group type to group key.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "Person properties to use for evaluation.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "Group properties keyed by group type.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to evaluate only locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "send_feature_flag_events", + "description": "Whether to send a $feature_flag_called event.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup.", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "Optional device ID override for experience-continuity flags.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[object]" + } + }, + { + "id": "get_feature_flag_result", + "title": "get_feature_flag_result", + "description": "Get a FeatureFlagResult object which contains the flag result and payload. This method evaluates a feature flag and returns a FeatureFlagResult object containing: - enabled: Whether the flag is enabled - variant: The variant value if the flag has variants - payload: The payload associated with the flag (automatically deserialized from JSON) - key: The flag key - reason: Why the flag was enabled/disabled", + "details": "", + "category": null, + "params": [ + { + "name": "key", + "description": "The feature flag key.", + "isOptional": true, + "type": "str" + }, + { + "name": "distinct_id", + "description": "The user's distinct ID.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "Mapping of group type to group key.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "Person properties to use for evaluation.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "Group properties keyed by group type.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to evaluate only locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "send_feature_flag_events", + "description": "Whether to send a $feature_flag_called event.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup.", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "Optional device ID override for experience-continuity flags.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[FeatureFlagResult]" + } + }, + { + "id": "get_remote_config_payload", + "title": "get_remote_config_payload", + "description": "Get the payload for a remote config feature flag.", + "details": "", + "category": null, + "params": [ + { + "name": "key", + "description": "The key of the feature flag", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "get_tags", + "title": "get_tags", + "description": "Get all tags from the current context. Returns: Dict of all tags in the current context", + "details": "", + "category": "Contexts", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "dict[str, Any]" + } + }, + { + "id": "group_identify", + "title": "group_identify", + "description": "Set properties on a group.", + "details": "", + "category": "Identification", + "params": [ + { + "name": "group_type", + "description": "Type of your group. Required - the call is dropped with a warning if it is missing or empty.", + "isOptional": true, + "type": "str" + }, + { + "name": "group_key", + "description": "Unique identifier of the group. Required - the call is dropped with a warning if it is missing or empty.", + "isOptional": true, + "type": "str" + }, + { + "name": "properties", + "description": "Properties to set on the group", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "timestamp", + "description": "Optional timestamp for the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC.", + "isOptional": false, + "type": "datetime" + }, + { + "name": "uuid", + "description": "Optional UUID for the event", + "isOptional": true, + "type": "str" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup", + "isOptional": true, + "type": "bool" + }, + { + "name": "distinct_id", + "description": "Optional distinct ID of the user performing the action", + "isOptional": false, + "type": "Number" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Group identify", + "code": "# Group identify\nfrom posthog import group_identify\ngroup_identify('company', 'company_id_in_your_db', {\n 'name': 'Awesome Inc.',\n 'employees': 11\n})" + } + ] + }, + { + "id": "identify_context", + "title": "identify_context", + "description": "Identify the current context with a distinct ID.", + "details": "", + "category": "Identification", + "params": [ + { + "name": "distinct_id", + "description": "The distinct ID to associate with the current context and its children", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import identify_context\nidentify_context(\"user_123\")" + } + ] + }, + { + "id": "join", + "title": "join", + "description": "Attempt to process queued events and stop the client's background workers. Use `shutdown()` directly in most cases. Failed or undrainable events may be dropped and reported through logging or ``on_error``; returning does not guarantee server receipt. Lifecycle cleanup is attempted once, and cleanup failures are logged without retry.", + "details": "", + "category": "Client management", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import join\njoin()" + } + ] + }, + { + "id": "load_feature_flags", + "title": "load_feature_flags", + "description": "Load feature flag definitions from PostHog.", + "details": "", + "category": "Feature flags", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import load_feature_flags\nload_feature_flags()" + } + ] + }, + { + "id": "new_context", + "title": "new_context", + "description": "Create a new context scope that will be active for the duration of the with block.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "fresh", + "description": "Whether to start with a fresh context (default: False)", + "isOptional": false, + "type": "bool" + }, + { + "name": "capture_exceptions", + "description": "Whether to capture exceptions raised within the context. If omitted, defaults to the relevant client's exception autocapture setting.", + "isOptional": true, + "type": "bool" + }, + { + "name": "client", + "description": "Optional Posthog client instance to use for this context (default: None)", + "isOptional": true, + "type": "Client" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import new_context, tag, capture\nwith new_context():\n tag(\"request_id\", \"123\")\n capture(\"event_name\", properties={\"property\": \"value\"})" + } + ] + }, + { + "id": "scoped", + "title": "scoped", + "description": "Decorator that creates a new context for the function.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "fresh", + "description": "Whether to start with a fresh context (default: False)", + "isOptional": false, + "type": "bool" + }, + { + "name": "capture_exceptions", + "description": "Whether to capture and track exceptions with posthog error tracking. If omitted, defaults to the global exception autocapture setting.", + "isOptional": true, + "type": "bool" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import scoped, tag, capture\n@scoped()\ndef process_payment(payment_id):\n tag(\"payment_id\", payment_id)\n capture(\"payment_started\")" + } + ] + }, + { + "id": "set", + "title": "set", + "description": "Set properties on a user record.", + "details": "This will overwrite previous people property values. Generally operates similar to `capture`, with distinct_id being an optional argument, defaulting to the current context's distinct ID. If there is no context-level distinct ID, and no override distinct_id is passed, this function will do nothing. Context tags are folded into $set properties, so tagging the current context and then calling `set` will cause those tags to be set on the user (unlike capture, which causes them to just be set on the event).", + "category": "Identification", + "params": [ + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalSetArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Set person properties", + "code": "# Set person properties\nfrom posthog import set\nset(distinct_id='distinct_id', properties={'name': 'Max Hedgehog'})" + } + ] + }, + { + "id": "set_capture_exception_code_variables_context", + "title": "set_capture_exception_code_variables_context", + "description": "Override code-variable capture for exceptions in the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "enabled", + "description": "Whether exceptions captured in this context should include local variable values from stack frames.", + "isOptional": true, + "type": "bool" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "set_code_variables_detect_secrets_context", + "title": "set_code_variables_detect_secrets_context", + "description": "Whether to apply entropy-based secret detection as a last-resort redaction of high-entropy values (API keys, tokens, strong passwords) in captured code variables for the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "enabled", + "description": "", + "isOptional": true, + "type": "bool" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "set_code_variables_ignore_patterns_context", + "title": "set_code_variables_ignore_patterns_context", + "description": "Override code-variable ignore patterns for exceptions in the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "ignore_patterns", + "description": "Variable-name patterns that should be omitted entirely when code variables are captured.", + "isOptional": true, + "type": "list[str]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "set_code_variables_mask_patterns_context", + "title": "set_code_variables_mask_patterns_context", + "description": "Override code-variable mask patterns for exceptions in the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "mask_patterns", + "description": "Variable-name patterns whose values should be replaced with ``***`` when code variables are captured.", + "isOptional": true, + "type": "list[str]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "set_code_variables_mask_url_credentials_context", + "title": "set_code_variables_mask_url_credentials_context", + "description": "Whether to scrub credentials embedded in URLs/DSNs (e.g. user:pass@host) from captured code variables for the current context.", + "details": "", + "category": null, + "params": [ + { + "name": "enabled", + "description": "", + "isOptional": true, + "type": "bool" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "set_context_device_id", + "title": "set_context_device_id", + "description": "Set the device ID for the current context, associating all feature flag requests in this or child contexts with the given device ID.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "device_id", + "description": "The device ID to associate with the current context and its children", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import set_context_device_id\nset_context_device_id(\"device_123\")" + } + ] + }, + { + "id": "set_context_session", + "title": "set_context_session", + "description": "Set the session ID for the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "session_id", + "description": "The session ID to associate with the current context and its children", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import set_context_session\nset_context_session(\"session_123\")" + } + ] + }, + { + "id": "set_once", + "title": "set_once", + "description": "Set properties on a user record, only if they do not yet exist.", + "details": "This will not overwrite previous people property values, unlike `set`. Otherwise, operates in an identical manner to `set`.", + "category": "Identification", + "params": [ + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalSetArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Set property once", + "code": "# Set property once\nfrom posthog import set_once\nset_once(distinct_id='distinct_id', properties={'initial_url': '/blog'})" + } + ] + }, + { + "id": "setup", + "title": "setup", + "description": "Create or return the global PostHog client configured by module settings. Most applications should either instantiate ``Posthog`` directly or set ``posthog.api_key``/other module settings before calling top-level helpers. ``setup()`` is called automatically by global APIs such as ``capture()``. Returns: The global ``Client`` instance. If both ``api_key`` and ``project_api_key`` are missing or blank, the client is disabled and module-level calls become no-ops.", + "details": "", + "category": "Initialization", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Client" + } + }, + { + "id": "shutdown", + "title": "shutdown", + "description": "Attempt final delivery and permanently shut down the global client. Call this for final process cleanup, not at the end of each request or serverless invocation that shares the module-level client. Use ``flush()`` before returning from those invocations instead. Shutdown closes capture lanes, stops background workers and the feature flag poller, and tears down integrations. The global client is not recreated automatically after shutdown. This normally blocks until queued events have been attempted and cleanup finishes. Failed or undrainable events may be dropped and reported through logging or ``on_error``; returning does not guarantee server receipt. Event draining has no timeout. Queued spans get a final flush with up to 30 seconds waiting for an in-flight flush, then a fresh 30-second drain budget; the last HTTP request is not cut short. Unsent queued spans and still-open spans are discarded with a warning. Lifecycle cleanup is attempted once, and cleanup failures are logged without retry. Calls made directly from SDK callbacks such as ``on_error`` are deferred to avoid deadlocking the worker. If blocking completion is required, signal an application-owned thread, return from the callback, and call ``shutdown()`` from that thread. Do not wait inside a callback for another thread or task calling a lifecycle method.", + "details": "", + "category": "Client management", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import shutdown\nshutdown()" + } + ] + }, + { + "id": "start_span", + "title": "start_span", + "description": "Start a span for distributed tracing. Alpha. Returns a span handle. Use it as a context manager to make it the active span for the block and end it on exit (recording a raised exception on the way out); or call ``end()`` yourself for a span that cannot wrap a block. Spans started inside the block nest under it automatically. Always returns a usable handle, even when tracing is off, so calling code never branches.", + "details": "", + "category": "Tracing", + "params": [ + { + "name": "name", + "description": "A low-cardinality operation name, e.g. ``GET /users/:id``. Variable values belong in attributes, not the name.", + "isOptional": true, + "type": "str" + }, + { + "name": "kind", + "description": "``internal`` (default), ``server``, ``client``, ``producer`` or ``consumer``.", + "isOptional": true, + "type": "str" + }, + { + "name": "attributes", + "description": "Initial attributes.", + "isOptional": true, + "type": "Mapping[str, Any]" + }, + { + "name": "parent", + "description": "A span handle, or an inbound W3C ``traceparent`` header value to continue a remote trace. Defaults to the active span.", + "isOptional": false, + "type": "Span" + }, + { + "name": "tracestate", + "description": "The inbound ``tracestate`` header accompanying a ``traceparent`` string ``parent``; preserved and propagated.", + "isOptional": true, + "type": "str" + }, + { + "name": "start_time", + "description": "A ``datetime`` or epoch seconds, to backdate the span.", + "isOptional": false, + "type": "datetime" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Span" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "import posthog\nposthog.traces = {\"service_name\": \"checkout-api\"}\n\nwith posthog.start_span(\"POST /checkout\", parent=request.headers.get(\"traceparent\")) as span:\n span.set_attribute(\"plan\", user.plan)\n with posthog.start_span(\"db.query\", kind=\"client\"):\n ...\n outgoing_headers = {\"traceparent\": span.traceparent()}" + } + ] + }, + { + "id": "tag", + "title": "tag", + "description": "Add a tag to the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "name", + "description": "The tag key", + "isOptional": true, + "type": "str" + }, + { + "name": "value", + "description": "The tag value", + "isOptional": true, + "type": "Any" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import tag\ntag(\"user_id\", \"123\")" + } + ] + } + ] + } + ], + "categories": [ + "Initialization", + "Identification", + "Capture", + "Error Tracking", + "Feature flags", + "Tracing", + "Contexts", + "Events", + "Client management" + ] +} \ No newline at end of file diff --git a/references/posthog-python-references-latest.json b/references/posthog-python-references-latest.json index a58eca35..cecc1969 100644 --- a/references/posthog-python-references-latest.json +++ b/references/posthog-python-references-latest.json @@ -2,7 +2,7 @@ "id": "posthog-python", "hogRef": "0.3", "info": { - "version": "7.79.0", + "version": "7.80.0", "id": "posthog-python", "title": "PostHog Python SDK", "description": "Integrate PostHog into any python application.", @@ -1001,7 +1001,7 @@ { "id": "flush", "title": "flush", - "description": "Attempt immediate delivery of queued events and ended spans, keeping the client reusable. For a client shared across requests or serverless invocations (including the module-level client), call ``flush()`` before a short-lived invocation returns. Use ``shutdown()`` instead only for final process cleanup or when disposing of a client that will not be reused. Flush does not stop the background workers or feature flag poller, and does not end open spans. Returning does not guarantee server receipt: pending work can remain after a timeout or span export failure, and failed events may be dropped by the consumers. Calls made directly from SDK callbacks such as ``on_error`` are deferred rather than blocking the worker that invoked the callback.", + "description": "Attempt immediate delivery of queued events, ended spans and autocaptured metrics, keeping the client reusable. For a client shared across requests or serverless invocations (including the module-level client), call ``flush()`` before a short-lived invocation returns. Use ``shutdown()`` instead only for final process cleanup or when disposing of a client that will not be reused. Flush does not stop the background workers or feature flag poller, and does not end open spans. Returning does not guarantee server receipt: pending work can remain after a timeout or span export failure, and failed events may be dropped by the consumers. Calls made directly from SDK callbacks such as ``on_error`` are deferred rather than blocking the worker that invoked the callback.", "details": "", "category": null, "params": [ From f2572a19b9fc55720defb462c1b0cbe98fc78f44 Mon Sep 17 00:00:00 2001 From: alexcastgonz <82875882+pcbeingused333@users.noreply.github.com> Date: Thu, 29 Oct 2026 07:39:42 -0400 Subject: [PATCH 41/60] fix(ai): handle None and NOT_GIVEN stream_options without mutating the caller's dict (#1144) Co-authored-by: Claude Sonnet 5.5 (cherry picked from commit 1625f4fbc1809d7a19b34354ad2df2a0e016928f) --- .sampo/changesets/openai-stream-options.md | 5 ++ posthog/ai/openai/openai.py | 9 ++- posthog/ai/openai/openai_async.py | 9 ++- posthog/test/ai/openai/test_openai.py | 64 ++++++++++++++++++++++ 4 files changed, 81 insertions(+), 6 deletions(-) create mode 100644 .sampo/changesets/openai-stream-options.md diff --git a/.sampo/changesets/openai-stream-options.md b/.sampo/changesets/openai-stream-options.md new file mode 100644 index 00000000..8d5fb477 --- /dev/null +++ b/.sampo/changesets/openai-stream-options.md @@ -0,0 +1,5 @@ +--- +pypi/posthog: patch +--- + +Fix streaming OpenAI chat completions when `stream_options` is `None` or `NOT_GIVEN` (it raised a `TypeError`), and stop adding `include_usage` to the `stream_options` dict the caller passed in. diff --git a/posthog/ai/openai/openai.py b/posthog/ai/openai/openai.py index 9a0bca4a..674908f2 100644 --- a/posthog/ai/openai/openai.py +++ b/posthog/ai/openai/openai.py @@ -389,9 +389,12 @@ def _create_streaming( ): start_time = time.time() state = _ChatCompletionsStreamState() - if "stream_options" not in kwargs: - kwargs["stream_options"] = {} - kwargs["stream_options"]["include_usage"] = True + # The caller's dict is left alone, and None / NOT_GIVEN mean "no options" + stream_options = kwargs.get("stream_options") + kwargs["stream_options"] = { + **(stream_options if isinstance(stream_options, dict) else {}), + "include_usage": True, + } response = self._original.create(**kwargs) def generator(): diff --git a/posthog/ai/openai/openai_async.py b/posthog/ai/openai/openai_async.py index 352f7150..3d6dcbf2 100644 --- a/posthog/ai/openai/openai_async.py +++ b/posthog/ai/openai/openai_async.py @@ -393,9 +393,12 @@ async def _create_streaming( start_time = time.time() state = _ChatCompletionsStreamState() - if "stream_options" not in kwargs: - kwargs["stream_options"] = {} - kwargs["stream_options"]["include_usage"] = True + # The caller's dict is left alone, and None / NOT_GIVEN mean "no options" + stream_options = kwargs.get("stream_options") + kwargs["stream_options"] = { + **(stream_options if isinstance(stream_options, dict) else {}), + "include_usage": True, + } response = await self._original.create(**kwargs) async def async_generator(): diff --git a/posthog/test/ai/openai/test_openai.py b/posthog/test/ai/openai/test_openai.py index 875c08c4..b1a6d6a2 100644 --- a/posthog/test/ai/openai/test_openai.py +++ b/posthog/test/ai/openai/test_openai.py @@ -7,6 +7,7 @@ import pytest try: + from openai import NOT_GIVEN from openai.types.chat import ChatCompletion, ChatCompletionMessage from openai.types.chat.chat_completion import Choice from openai.types.chat.chat_completion_chunk import ( @@ -460,6 +461,33 @@ def test_embeddings(mock_client, mock_embedding_response): assert isinstance(props["$ai_latency"], float) +@pytest.mark.parametrize("stream_options", [None, NOT_GIVEN, {"foo": "bar"}]) +def test_streaming_stream_options_are_not_mutated( + mock_client, streaming_tool_call_chunks, stream_options +): + captured = {} + + def mock_create(self, **kwargs): + captured["kwargs"] = kwargs + return iter(streaming_tool_call_chunks) + + with patch("openai.resources.chat.completions.Completions.create", new=mock_create): + client = OpenAI(api_key="test-key", posthog_client=mock_client) + stream = client.chat.completions.create( + model="gpt-4", + messages=[{"role": "user", "content": "Hello"}], + stream=True, + stream_options=stream_options, + ) + list(stream) + + sent = captured["kwargs"]["stream_options"] + assert sent["include_usage"] is True + if isinstance(stream_options, dict): + assert sent == {"foo": "bar", "include_usage": True} + assert stream_options == {"foo": "bar"} + + def test_groups(mock_client, mock_openai_response): with patch( "openai.resources.chat.completions.Completions.create", @@ -1440,6 +1468,42 @@ async def chunk_iterable(): assert isinstance(props["$ai_latency"], float) +@pytest.mark.asyncio +@pytest.mark.parametrize("stream_options", [None, NOT_GIVEN, {"foo": "bar"}]) +async def test_async_streaming_stream_options_are_not_mutated( + mock_client, streaming_tool_call_chunks, stream_options +): + captured = {} + + async def mock_create(self, **kwargs): + captured["kwargs"] = kwargs + + async def chunk_iterable(): + for chunk in streaming_tool_call_chunks: + yield chunk + + return chunk_iterable() + + with patch( + "openai.resources.chat.completions.AsyncCompletions.create", new=mock_create + ): + client = AsyncOpenAI(api_key="test-key", posthog_client=mock_client) + stream = await client.chat.completions.create( + model="gpt-4", + messages=[{"role": "user", "content": "Hello"}], + stream=True, + stream_options=stream_options, + ) + async for _ in stream: + pass + + sent = captured["kwargs"]["stream_options"] + assert sent["include_usage"] is True + if isinstance(stream_options, dict): + assert sent == {"foo": "bar", "include_usage": True} + assert stream_options == {"foo": "bar"} + + @pytest.mark.asyncio async def test_async_embeddings_create(mock_client, mock_embedding_response): mock_create = AsyncMock(return_value=mock_embedding_response) From 77da71debfeec75b41cd2dff59e51af0013c52f9 Mon Sep 17 00:00:00 2001 From: "releaser-posthog-python[bot]" <262414804+releaser-posthog-python[bot]@users.noreply.github.com> Date: Thu, 29 Oct 2026 11:41:36 +0000 Subject: [PATCH 42/60] chore: Release v7.80.1 [skip ci] (cherry picked from commit 58d69e3127134967e317ee29e25ad70de3909b79) --- .sampo/changesets/openai-stream-options.md | 5 ----- openfeature-provider/CHANGELOG.md | 6 ++++++ openfeature-provider/pyproject.toml | 2 +- posthog/CHANGELOG.md | 6 ++++++ posthog/version.py | 2 +- pyproject.toml | 2 +- uv.lock | 4 ++-- 7 files changed, 17 insertions(+), 10 deletions(-) delete mode 100644 .sampo/changesets/openai-stream-options.md diff --git a/.sampo/changesets/openai-stream-options.md b/.sampo/changesets/openai-stream-options.md deleted file mode 100644 index 8d5fb477..00000000 --- a/.sampo/changesets/openai-stream-options.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -pypi/posthog: patch ---- - -Fix streaming OpenAI chat completions when `stream_options` is `None` or `NOT_GIVEN` (it raised a `TypeError`), and stop adding `include_usage` to the `stream_options` dict the caller passed in. diff --git a/openfeature-provider/CHANGELOG.md b/openfeature-provider/CHANGELOG.md index 5abeea9b..b80b9e7c 100644 --- a/openfeature-provider/CHANGELOG.md +++ b/openfeature-provider/CHANGELOG.md @@ -4,6 +4,12 @@ All notable changes to `openfeature-provider-posthog` are documented here. This file is maintained by [Sampo](https://github.com/bruits/sampo) from changesets in `.sampo/changesets/` that target `pypi/openfeature-provider-posthog`. +## 0.1.132 — 2026-10-29 + +### Patch changes + +- Updated dependencies: posthog@7.80.1 + ## 0.1.131 — 2026-10-29 ### Patch changes diff --git a/openfeature-provider/pyproject.toml b/openfeature-provider/pyproject.toml index b7a496b4..87702610 100644 --- a/openfeature-provider/pyproject.toml +++ b/openfeature-provider/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "openfeature-provider-posthog" -version = "0.1.131" +version = "0.1.132" description = "Official PostHog provider for the OpenFeature Python SDK." readme = "README.md" authors = [{ name = "PostHog", email = "engineering@posthog.com" }] diff --git a/posthog/CHANGELOG.md b/posthog/CHANGELOG.md index 08fe1166..72393f86 100644 --- a/posthog/CHANGELOG.md +++ b/posthog/CHANGELOG.md @@ -1,5 +1,11 @@ # posthog +## 7.80.1 — 2026-10-29 + +### Patch changes + +- [1625f4f](https://github.com/posthog/posthog-python/commit/1625f4fbc1809d7a19b34354ad2df2a0e016928f) Fix streaming OpenAI chat completions when `stream_options` is `None` or `NOT_GIVEN` (it raised a `TypeError`), and stop adding `include_usage` to the `stream_options` dict the caller passed in. — Thanks @pcbeingused333! + ## 7.80.0 — 2026-10-29 ### Minor changes diff --git a/posthog/version.py b/posthog/version.py index d9cc0934..f32b8b91 100644 --- a/posthog/version.py +++ b/posthog/version.py @@ -1 +1 @@ -VERSION = "7.80.0" +VERSION = "7.80.1" diff --git a/pyproject.toml b/pyproject.toml index 18c7119c..6fc12f13 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "posthog" -version = "7.80.0" +version = "7.80.1" description = "Integrate PostHog into any python application." authors = [{ name = "PostHog", email = "engineering@posthog.com" }] maintainers = [{ name = "PostHog", email = "engineering@posthog.com" }] diff --git a/uv.lock b/uv.lock index c3d02bed..edfef025 100644 --- a/uv.lock +++ b/uv.lock @@ -2656,7 +2656,7 @@ wheels = [ [[package]] name = "openfeature-provider-posthog" -version = "0.1.131" +version = "0.1.132" source = { editable = "openfeature-provider" } dependencies = [ { name = "openfeature-sdk" }, @@ -3193,7 +3193,7 @@ wheels = [ [[package]] name = "posthog" -version = "7.80.0" +version = "7.80.1" source = { editable = "." } dependencies = [ { name = "distro" }, From b4e1b03421dc47d83b5293f0f6a73ece324cab82 Mon Sep 17 00:00:00 2001 From: "releaser-posthog-python[bot]" <262414804+releaser-posthog-python[bot]@users.noreply.github.com> Date: Thu, 29 Oct 2026 11:41:41 +0000 Subject: [PATCH 43/60] Update generated references (cherry picked from commit e4f43a4bf81b7e8c6ec151f53086964c88e39854) --- .../posthog-python-references-7.80.1.json | 3548 +++++++++++++++++ .../posthog-python-references-latest.json | 2 +- 2 files changed, 3549 insertions(+), 1 deletion(-) create mode 100644 references/posthog-python-references-7.80.1.json diff --git a/references/posthog-python-references-7.80.1.json b/references/posthog-python-references-7.80.1.json new file mode 100644 index 00000000..841a5b2d --- /dev/null +++ b/references/posthog-python-references-7.80.1.json @@ -0,0 +1,3548 @@ +{ + "id": "posthog-python", + "hogRef": "0.3", + "info": { + "version": "7.80.1", + "id": "posthog-python", + "title": "PostHog Python SDK", + "description": "Integrate PostHog into any python application.", + "slugPrefix": "posthog-python", + "specUrl": "https://github.com/PostHog/posthog-python" + }, + "types": [ + { + "id": "FeatureFlag", + "name": "FeatureFlag", + "path": "posthog.types.FeatureFlag", + "properties": [ + { + "name": "key", + "type": "str", + "description": "Field: key" + }, + { + "name": "enabled", + "type": "bool", + "description": "Field: enabled" + }, + { + "name": "variant", + "type": "Optional[str]", + "description": "Field: variant" + }, + { + "name": "reason", + "type": "Optional[FlagReason]", + "description": "Field: reason" + }, + { + "name": "metadata", + "type": "Union[FlagMetadata, LegacyFlagMetadata]", + "description": "Field: metadata" + } + ], + "example": "" + }, + { + "id": "FeatureFlagResult", + "name": "FeatureFlagResult", + "path": "posthog.types.FeatureFlagResult", + "properties": [ + { + "name": "key", + "type": "str", + "description": "Field: key" + }, + { + "name": "enabled", + "type": "bool", + "description": "Field: enabled" + }, + { + "name": "variant", + "type": "Optional[str]", + "description": "Field: variant" + }, + { + "name": "payload", + "type": "Optional[Any]", + "description": "Field: payload" + }, + { + "name": "reason", + "type": "Optional[str]", + "description": "Field: reason" + } + ], + "example": "" + }, + { + "id": "FlagMetadata", + "name": "FlagMetadata", + "path": "posthog.types.FlagMetadata", + "properties": [ + { + "name": "id", + "type": "int", + "description": "Field: id" + }, + { + "name": "payload", + "type": "Optional[str]", + "description": "Field: payload" + }, + { + "name": "version", + "type": "int", + "description": "Field: version" + }, + { + "name": "description", + "type": "str", + "description": "Field: description" + }, + { + "name": "has_experiment", + "type": "Optional[bool]", + "description": "Field: has_experiment" + } + ], + "example": "" + }, + { + "id": "FlagReason", + "name": "FlagReason", + "path": "posthog.types.FlagReason", + "properties": [ + { + "name": "code", + "type": "str", + "description": "Field: code" + }, + { + "name": "condition_index", + "type": "Optional[int]", + "description": "Field: condition_index" + }, + { + "name": "description", + "type": "str", + "description": "Field: description" + } + ], + "example": "" + }, + { + "id": "FlagsAndPayloads", + "name": "FlagsAndPayloads", + "path": "posthog.types.FlagsAndPayloads", + "properties": [ + { + "name": "featureFlags", + "type": "Optional[dict[str, Union[bool, str]]]", + "description": "Field: featureFlags" + }, + { + "name": "featureFlagPayloads", + "type": "Optional[dict[str, Any]]", + "description": "Field: featureFlagPayloads" + } + ], + "example": "" + }, + { + "id": "FlagsResponse", + "name": "FlagsResponse", + "path": "posthog.types.FlagsResponse", + "properties": [ + { + "name": "flags", + "type": "dict[str, FeatureFlag]", + "description": "Field: flags" + }, + { + "name": "errorsWhileComputingFlags", + "type": "bool", + "description": "Field: errorsWhileComputingFlags" + }, + { + "name": "requestId", + "type": "str", + "description": "Field: requestId" + }, + { + "name": "quotaLimit", + "type": "Optional[list[str]]", + "description": "Field: quotaLimit" + }, + { + "name": "evaluatedAt", + "type": "Optional[int]", + "description": "Field: evaluatedAt" + }, + { + "name": "minimalFlagCalledEvents", + "type": "bool", + "description": "Field: minimalFlagCalledEvents" + } + ], + "example": "" + }, + { + "id": "LegacyFlagMetadata", + "name": "LegacyFlagMetadata", + "path": "posthog.types.LegacyFlagMetadata", + "properties": [ + { + "name": "payload", + "type": "Any", + "description": "Field: payload" + } + ], + "example": "" + }, + { + "id": "SendFeatureFlagsOptions", + "name": "SendFeatureFlagsOptions", + "path": "posthog.types.SendFeatureFlagsOptions", + "properties": [ + { + "name": "should_send", + "type": "bool", + "description": "Field: should_send" + }, + { + "name": "only_evaluate_locally", + "type": "Optional[bool]", + "description": "Field: only_evaluate_locally" + }, + { + "name": "person_properties", + "type": "Optional[dict[str, Any]]", + "description": "Field: person_properties" + }, + { + "name": "group_properties", + "type": "Optional[dict[str, dict[str, Any]]]", + "description": "Field: group_properties" + }, + { + "name": "flag_keys_filter", + "type": "Optional[list[str]]", + "description": "Field: flag_keys_filter" + } + ], + "example": "" + }, + { + "id": "OptionalCaptureArgs", + "name": "OptionalCaptureArgs", + "path": "posthog.args.OptionalCaptureArgs", + "properties": [ + { + "name": "distinct_id", + "type": "NotRequired[Union[Number, str, UUID, int, any]]", + "description": "Field: distinct_id" + }, + { + "name": "properties", + "type": "NotRequired[Optional[dict[str, Any]]]", + "description": "Field: properties" + }, + { + "name": "timestamp", + "type": "NotRequired[Union[datetime, str, any]]", + "description": "Field: timestamp" + }, + { + "name": "uuid", + "type": "NotRequired[Union[str, UUID, any]]", + "description": "Field: uuid" + }, + { + "name": "groups", + "type": "NotRequired[Optional[dict[str, str]]]", + "description": "Field: groups" + }, + { + "name": "flags", + "type": "NotRequired[Optional[ForwardRef('FeatureFlagEvaluations')]]", + "description": "Field: flags" + }, + { + "name": "send_feature_flags", + "type": "NotRequired[Union[bool, SendFeatureFlagsOptions, any]]", + "description": "Field: send_feature_flags" + }, + { + "name": "disable_geoip", + "type": "NotRequired[Optional[bool]]", + "description": "Field: disable_geoip" + }, + { + "name": "_property_allowlist", + "type": "NotRequired[Optional[frozenset[str]]]", + "description": "Field: _property_allowlist" + } + ], + "example": "" + }, + { + "id": "OptionalSetArgs", + "name": "OptionalSetArgs", + "path": "posthog.args.OptionalSetArgs", + "properties": [ + { + "name": "distinct_id", + "type": "NotRequired[Union[Number, str, UUID, int, any]]", + "description": "Field: distinct_id" + }, + { + "name": "properties", + "type": "NotRequired[Optional[dict[str, Any]]]", + "description": "Field: properties" + }, + { + "name": "timestamp", + "type": "NotRequired[Union[datetime, str, any]]", + "description": "Field: timestamp" + }, + { + "name": "uuid", + "type": "NotRequired[Union[str, UUID, any]]", + "description": "Field: uuid" + }, + { + "name": "disable_geoip", + "type": "NotRequired[Optional[bool]]", + "description": "Field: disable_geoip" + } + ], + "example": "" + }, + { + "id": "SendFeatureFlagsOptions", + "name": "SendFeatureFlagsOptions", + "path": "posthog.types.SendFeatureFlagsOptions", + "properties": [ + { + "name": "should_send", + "type": "bool", + "description": "Field: should_send" + }, + { + "name": "only_evaluate_locally", + "type": "Optional[bool]", + "description": "Field: only_evaluate_locally" + }, + { + "name": "person_properties", + "type": "Optional[dict[str, Any]]", + "description": "Field: person_properties" + }, + { + "name": "group_properties", + "type": "Optional[dict[str, dict[str, Any]]]", + "description": "Field: group_properties" + }, + { + "name": "flag_keys_filter", + "type": "Optional[list[str]]", + "description": "Field: flag_keys_filter" + } + ], + "example": "" + } + ], + "classes": [ + { + "id": "PostHog", + "title": "PostHog", + "description": "This is the SDK reference for the PostHog Python SDK. You can learn more about example usage in the [Python SDK documentation](/docs/libraries/python). You can also follow [Flask](/docs/libraries/flask) and [Django](/docs/libraries/django) guides to integrate PostHog into your project. For long-running applications, create one client during application startup and reuse it for the lifetime of the process. This keeps background queues predictable and makes shutdown flushing straightforward. Multiple clients are still supported for intentional multi-project or multi-host setups.", + "functions": [ + { + "id": "__init__", + "title": "Client", + "description": "Initialize a new PostHog client instance.", + "details": "", + "category": "Initialization", + "params": [ + { + "name": "project_api_key", + "description": "PostHog project API key/token.", + "isOptional": true, + "type": "str" + }, + { + "name": "host", + "description": "PostHog host. Defaults to the US ingestion endpoint when not set. App hosts such as ``https://us.posthog.com`` are mapped to the corresponding ingestion host.", + "isOptional": false, + "type": "any" + }, + { + "name": "debug", + "description": "Enable verbose SDK logging and re-raise errors from public API methods.", + "isOptional": false, + "type": "bool" + }, + { + "name": "max_queue_size", + "description": "Maximum number of events buffered before upload.", + "isOptional": false, + "type": "int" + }, + { + "name": "send", + "description": "If False, queueing succeeds but events are not sent.", + "isOptional": false, + "type": "bool" + }, + { + "name": "on_error", + "description": "Optional callback invoked by background consumers when an upload fails. Keep it short and non-blocking. Calling lifecycle methods directly is safe and deferred, but do not start another thread or task that calls ``flush()``, ``join()``, or ``shutdown()`` and then wait for it from the callback.", + "isOptional": false, + "type": "any" + }, + { + "name": "flush_at", + "description": "Number of queued events that triggers a batch upload.", + "isOptional": false, + "type": "int" + }, + { + "name": "flush_interval", + "description": "Maximum seconds a background consumer waits before flushing a partial batch.", + "isOptional": false, + "type": "float" + }, + { + "name": "gzip", + "description": "Whether to gzip event upload payloads.", + "isOptional": false, + "type": "bool" + }, + { + "name": "max_retries", + "description": "Number of upload retries. Values below 0 are treated as 0.", + "isOptional": false, + "type": "int" + }, + { + "name": "sync_mode", + "description": "If True, send each event synchronously instead of using background worker threads. This blocks the calling thread; in asyncio applications such as FastAPI, use ``AsyncPosthog`` instead.", + "isOptional": false, + "type": "bool" + }, + { + "name": "timeout", + "description": "HTTP request timeout in seconds for event uploads.", + "isOptional": false, + "type": "float" + }, + { + "name": "thread", + "description": "Number of background consumer threads.", + "isOptional": false, + "type": "int" + }, + { + "name": "poll_interval", + "description": "Seconds between local feature flag definition refreshes.", + "isOptional": false, + "type": "int" + }, + { + "name": "personal_api_key", + "description": "Deprecated alias for ``secret_key``. Still honored for backwards compatibility; prefer ``secret_key``, which also accepts a Project Secret API Key.", + "isOptional": false, + "type": "any" + }, + { + "name": "disabled", + "description": "If True, disable captures and API requests. Useful in tests.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable server-side GeoIP enrichment. Defaults to True.", + "isOptional": false, + "type": "bool" + }, + { + "name": "is_server", + "description": "Whether events are emitted from a server-side runtime. Defaults to True; set to False when using the SDK as a client/CLI so the device OS is attributed to the person normally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "historical_migration", + "description": "Mark events as historical migration imports.", + "isOptional": false, + "type": "bool" + }, + { + "name": "feature_flags_request_timeout_seconds", + "description": "Timeout in seconds for feature flag and remote config requests.", + "isOptional": false, + "type": "float" + }, + { + "name": "feature_flags_request_max_retries", + "description": "Number of retries for feature flag requests after network, transport, or timeout failures. Defaults to 1. Set to 0 to disable retries.", + "isOptional": false, + "type": "int" + }, + { + "name": "super_properties", + "description": "Properties merged into every captured event.", + "isOptional": false, + "type": "any" + }, + { + "name": "enable_exception_autocapture", + "description": "Automatically capture uncaught exceptions.", + "isOptional": false, + "type": "bool" + }, + { + "name": "log_captured_exceptions", + "description": "Also log exceptions captured by error tracking.", + "isOptional": false, + "type": "bool" + }, + { + "name": "project_root", + "description": "Root path used to determine in-app stack frames for captured exceptions. Defaults to the current working directory.", + "isOptional": false, + "type": "any" + }, + { + "name": "privacy_mode", + "description": "For AI observability, capture usage metadata without prompt inputs or outputs.", + "isOptional": false, + "type": "bool" + }, + { + "name": "before_send", + "description": "Optional callback that can modify or drop events before upload. Return ``None`` to drop an event.", + "isOptional": false, + "type": "any" + }, + { + "name": "flag_fallback_cache_url", + "description": "Optional feature flag fallback cache URL, such as ``memory://local/?ttl=300&size=10000`` or a Redis URL.", + "isOptional": false, + "type": "any" + }, + { + "name": "enable_local_evaluation", + "description": "Whether to poll feature flag definitions for local evaluation when a personal API key is configured.", + "isOptional": false, + "type": "bool" + }, + { + "name": "flag_definition_cache_provider", + "description": "Optional external cache provider for sharing feature flag definitions across workers.", + "isOptional": true, + "type": "FlagDefinitionCacheProvider" + }, + { + "name": "capture_exception_code_variables", + "description": "Capture local variable values on exception stack frames.", + "isOptional": false, + "type": "bool" + }, + { + "name": "code_variables_mask_patterns", + "description": "Variable-name patterns to mask when capturing code variables.", + "isOptional": false, + "type": "any" + }, + { + "name": "code_variables_ignore_patterns", + "description": "Variable-name patterns to omit when capturing code variables.", + "isOptional": false, + "type": "any" + }, + { + "name": "code_variables_mask_url_credentials", + "description": "Scrub credentials embedded in URLs/DSNs (e.g. ``user:pass@host``) from captured code variables, regardless of the surrounding variable name. Defaults to True.", + "isOptional": false, + "type": "any" + }, + { + "name": "code_variables_detect_secrets", + "description": "Last-resort entropy-based detection that redacts high-entropy secret-looking values (API keys, tokens, strong passwords) sitting in innocuously-named variables, after the name and URL checks. Skips structured ids (UUIDs, ObjectIds, hashes). Defaults to True.", + "isOptional": false, + "type": "any" + }, + { + "name": "in_app_modules", + "description": "Module/package prefixes treated as in-app frames in captured exceptions.", + "isOptional": false, + "type": "UnionType[list[str], any]" + }, + { + "name": "enable_exception_autocapture_rate_limiting", + "description": "Rate limit autocaptured exceptions client-side with a token bucket per exception type. Disabled by default.", + "isOptional": false, + "type": "bool" + }, + { + "name": "exception_autocapture_bucket_size", + "description": "Maximum burst of autocaptured exceptions allowed per exception type (token bucket size, clamped to 0-100).", + "isOptional": false, + "type": "int" + }, + { + "name": "exception_autocapture_refill_rate", + "description": "Tokens restored per refill interval for each exception type's bucket.", + "isOptional": false, + "type": "int" + }, + { + "name": "exception_autocapture_refill_interval_seconds", + "description": "Seconds between token refills for autocaptured exception rate limiting.", + "isOptional": false, + "type": "int" + }, + { + "name": "capture_mode", + "description": "Capture wire protocol to use. Defaults to ``CaptureMode.V0`` (legacy ``/batch/``). Set ``CaptureMode.V1`` (or pass the string ``\"v1\"``) to opt into ``/i/v1/analytics/events``. When omitted, the ``POSTHOG_CAPTURE_MODE`` env var is consulted, then ``V0``.", + "isOptional": false, + "type": "CaptureMode" + }, + { + "name": "capture_compression", + "description": "Request-body compression for capture-v1 uploads (ignored in V0, which uses ``gzip``). ``CaptureCompression.GZIP`` or ``DEFLATE`` (or the strings ``\"gzip\"``/``\"deflate\"``). When omitted, the ``POSTHOG_CAPTURE_COMPRESSION`` env var is consulted, then the legacy ``gzip`` flag, then no compression.", + "isOptional": false, + "type": "CaptureCompression" + }, + { + "name": "secret_key", + "description": "A Personal API Key or Project Secret API Key, used to authenticate local feature flag evaluation, remote config payloads, and decrypted flag payloads. Example:: posthog.Client(project_api_key, secret_key=\"phx_...\")", + "isOptional": false, + "type": "any" + }, + { + "name": "metrics", + "description": "", + "isOptional": true, + "type": "dict" + }, + { + "name": "enable_full_ai_capture", + "description": "Route PostHog AI wrapper events through the dedicated AI capture endpoint and capture full AI content: skips string truncation and passes media (base64/data URIs) through unredacted. ``privacy_mode`` always wins. Defaults to False.", + "isOptional": false, + "type": "bool" + }, + { + "name": "capture_trace_context", + "description": "When OpenTelemetry is installed and a valid span is active at capture time, add its trace and span IDs as ``$trace_id`` and ``$span_id`` properties to events captured with ``capture()`` and ``capture_ai()``, so they can be correlated with backend traces. Explicit ``$trace_id``/``$span_id`` values passed in ``properties`` win. Exception events (``capture_exception``) always attach these IDs regardless of this setting. Defaults to False.", + "isOptional": false, + "type": "bool" + }, + { + "name": "_use_ai_lane", + "description": "", + "isOptional": false, + "type": "bool" + }, + { + "name": "_enable_multimodal_capture", + "description": "", + "isOptional": false, + "type": "bool" + }, + { + "name": "traces", + "description": "Config dict for distributed tracing: ``service_name``, ``service_version``, ``environment``, ``resource_attributes``, ``flush_interval`` (5 s), ``max_queue_size`` (2048), ``max_export_batch_size`` (512), ``max_live_spans`` (10000), ``max_span_age`` (3600 s), ``max_attributes_per_span`` (128), ``max_events_per_span`` (128), ``max_attribute_value_length`` (8192). ``before_span_send`` is a callable, or a list run in order, that receives each finished span as a dict (``trace_id``, ``span_id`` and ``parent_span_id`` are read-only) and returns it, edited, or ``None`` to drop it; a hook that raises drops the span. Tracing is off until this is provided. Spans export on a background timer even with ``sync_mode``; serverless handlers should call ``flush()`` before returning. Defaults to None.", + "isOptional": true, + "type": "dict" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import Posthog\n\nposthog = Posthog('', host='')" + } + ] + }, + { + "id": "alias", + "title": "alias", + "description": "Create an alias between two distinct IDs.", + "details": "", + "category": "Identification", + "params": [ + { + "name": "previous_id", + "description": "The previous distinct ID. Required - the call is dropped with a warning if it is missing or empty.", + "isOptional": true, + "type": "Number" + }, + { + "name": "distinct_id", + "description": "The new distinct ID to alias to. Falls back to the context distinct ID; the call is dropped with a warning if neither is available.", + "isOptional": true, + "type": "str" + }, + { + "name": "timestamp", + "description": "The timestamp of the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC.", + "isOptional": false, + "type": "datetime" + }, + { + "name": "uuid", + "description": "A unique identifier for the event. If provided, it must be a valid UUID string or uuid.UUID instance; invalid values are ignored and replaced with a newly generated UUID.", + "isOptional": true, + "type": "str" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this event.", + "isOptional": true, + "type": "bool" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.alias(previous_id='distinct_id', distinct_id='alias_id')" + } + ] + }, + { + "id": "capture", + "title": "capture", + "description": "Captures an event manually. [Learn about capture best practices](https://posthog.com/docs/product-analytics/capture-events)", + "details": "", + "category": "Capture", + "params": [ + { + "name": "event", + "description": "The event name to capture.", + "isOptional": true, + "type": "str" + }, + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalCaptureArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Anonymous event", + "code": "# Anonymous event\nposthog.capture('some-anon-event')" + }, + { + "id": "example_2", + "name": "Context usage", + "code": "# Context usage\nfrom posthog import identify_context, new_context\nwith new_context():\n identify_context('distinct_id_of_the_user')\n posthog.capture('user_signed_up')\n posthog.capture('user_logged_in')\n posthog.capture('some-custom-action', distinct_id='distinct_id_of_the_user')" + }, + { + "id": "example_3", + "name": "Set event properties", + "code": "# Set event properties\nposthog.capture(\n \"user_signed_up\",\n distinct_id=\"distinct_id_of_the_user\",\n properties={\n \"login_type\": \"email\",\n \"is_free_trial\": \"true\"\n }\n)" + }, + { + "id": "example_4", + "name": "Page view event", + "code": "# Page view event\nposthog.capture('$pageview', distinct_id=\"distinct_id_of_the_user\", properties={'$current_url': 'https://example.com'})" + } + ] + }, + { + "id": "capture_ai", + "title": "capture_ai", + "description": "Capture an AI event on the dedicated AI capture endpoint. Beta: the signature is stable; operational limits (per-event size cap, batching, endpoint) may change without notice. Takes the same arguments and returns the same value as `capture()`: the event UUID, or None when the event was not admitted (disabled client, or dropped by `before_send`). The event is queued on an isolated AI lane with its own consumer pool and a higher per-event size cap, posting to the dedicated AI ingestion endpoint. The payload is sent as given \u2014 no redaction or truncation is applied here.", + "details": "", + "category": "Capture", + "params": [ + { + "name": "event", + "description": "", + "isOptional": true, + "type": "str" + }, + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalCaptureArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + } + }, + { + "id": "capture_exception", + "title": "capture_exception", + "description": "Capture an exception for error tracking. When OpenTelemetry is installed and a valid span is active, its trace and span IDs are added as ``$trace_id`` and ``$span_id`` event properties.", + "details": "", + "category": "Error Tracking", + "params": [ + { + "name": "exception", + "description": "The exception to capture.", + "isOptional": true, + "type": "BaseException" + }, + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalCaptureArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "try:\n # Some code that might fail\n pass\nexcept Exception as e:\n posthog.capture_exception(e, 'user_distinct_id', properties=additional_properties)" + } + ] + }, + { + "id": "evaluate_flags", + "title": "evaluate_flags", + "description": "Evaluate all feature flags for a user in a single call and return a :class:`FeatureFlagEvaluations` snapshot. Branch on ``.is_enabled()`` / ``.get_flag()`` and pass the same snapshot to :meth:`capture` via the ``flags`` option so events carry the exact flag values the code branched on. Prefer this over repeated ``get_feature_flag()`` calls and over ``capture(send_feature_flags=True)`` \u2014 it consolidates flag evaluation into a single ``/flags`` request per incoming request. Local evaluation is transparent: when the poller resolves a flag, the snapshot's ``$feature_flag_called`` events are tagged ``locally_evaluated=True`` and reason ``\"Evaluated locally\"``.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The user's distinct ID. If ``None``, falls back to the context distinct_id. If still unresolvable, returns an empty snapshot.", + "isOptional": false, + "type": "Number" + }, + { + "name": "groups", + "description": "Mapping of group type to group key.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "Person properties to use for evaluation.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "Group properties keyed by group type.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "If True, never fall back to remote evaluation \u2014 flags that can't be evaluated locally are omitted from the snapshot and listed with a reason in its ``unresolved_flags``.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup.", + "isOptional": true, + "type": "bool" + }, + { + "name": "flag_keys", + "description": "Optional list that scopes local evaluation, the underlying ``/flags`` request, and the returned snapshot. When omitted or ``None``, all flags are evaluated. An empty list returns an empty snapshot without evaluating flags. A requested key absent from loaded local definitions is included in one remote fallback per ``evaluate_flags`` call unless ``only_evaluate_locally`` is True. If the server also does not know the key, it is omitted from the snapshot.", + "isOptional": true, + "type": "list[str]" + }, + { + "name": "device_id", + "description": "Optional device ID override. If not provided, falls back to the context device_id (which may be set via tracing headers). Used by experience-continuity flags to match users across distinct_id changes.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "FeatureFlagEvaluations" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "flags = posthog.evaluate_flags(\n \"user_123\",\n person_properties={\"plan\": \"enterprise\"},\n)\nif flags.is_enabled(\"new-dashboard\"):\n render_new_dashboard()\nposthog.capture(\"page_viewed\", distinct_id=\"user_123\", flags=flags)" + } + ] + }, + { + "id": "feature_enabled", + "title": "feature_enabled", + "description": "Check if a feature flag is enabled for a user.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "The feature flag key.", + "isOptional": true, + "type": "str" + }, + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to only evaluate locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "send_feature_flag_events", + "description": "Whether to send feature flag events.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[bool]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "is_my_flag_enabled = posthog.feature_enabled('flag-key', 'distinct_id_of_your_user')\nif is_my_flag_enabled:\n # Do something differently for this user\n # Optional: fetch the payload\n matched_flag_payload = posthog.get_feature_flag_payload('flag-key', 'distinct_id_of_your_user')" + } + ] + }, + { + "id": "feature_flag_definitions", + "title": "feature_flag_definitions", + "description": "Return feature flag definitions loaded for local evaluation. Returns: The currently loaded feature flag definitions, or ``None`` before local evaluation has loaded definitions.", + "details": "", + "category": "Feature flags", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "flush", + "title": "flush", + "description": "Attempt immediate delivery of queued events, ended spans and autocaptured metrics, keeping the client reusable. For a client shared across requests or serverless invocations (including the module-level client), call ``flush()`` before a short-lived invocation returns. Use ``shutdown()`` instead only for final process cleanup or when disposing of a client that will not be reused. Flush does not stop the background workers or feature flag poller, and does not end open spans. Returning does not guarantee server receipt: pending work can remain after a timeout or span export failure, and failed events may be dropped by the consumers. Calls made directly from SDK callbacks such as ``on_error`` are deferred rather than blocking the worker that invoked the callback.", + "details": "", + "category": null, + "params": [ + { + "name": "timeout_seconds", + "description": "Seconds to wait for event queues to drain, shared across event lanes. Defaults to 10 seconds. Pass ``None`` to wait without a time limit; this does not guarantee successful delivery. Queued spans flush concurrently. The span exporter waits up to this many seconds for an in-flight flush, then uses a fresh drain budget once it acquires the lock. If the lock wait times out, the in-flight flush is left to finish. At least one span request is attempted after acquiring the lock, even with no budget left; retriable failures are retried after backoff while budget remains, with one last attempt at the deadline. Each span request is bounded by the client's ``timeout`` in seconds, and the last request is not cut short. This is therefore not a strict wall-clock limit for the whole call. With ``None``, a retriable span failure is left for a later flush rather than retried within this call.", + "isOptional": true, + "type": "float" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.capture('event_name', distinct_id='user_id')\n# Before a serverless invocation returns; keep the shared client open.\nposthog.flush(timeout_seconds=10)" + } + ] + }, + { + "id": "get_active_span", + "title": "get_active_span", + "description": "The span that is active in the current context, or ``None``. Alpha. Only entering a span (``with posthog.start_span(...) as span:``) makes it active; a span started manually is not. Use it to propagate the trace to the next service: ``span.traceparent()`` is the header value.", + "details": "", + "category": "Tracing", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[Span]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "span = posthog.get_active_span()\nif span is not None:\n headers[\"traceparent\"] = span.traceparent()" + } + ] + }, + { + "id": "get_all_flags", + "title": "get_all_flags", + "description": "Get all feature flags for a user.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to only evaluate locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "flag_keys_to_evaluate", + "description": "A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance.", + "isOptional": true, + "type": "list[str]" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[dict[str, Union[bool, str]]]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.get_all_flags('distinct_id_of_your_user')" + } + ] + }, + { + "id": "get_all_flags_and_payloads", + "title": "get_all_flags_and_payloads", + "description": "Get all feature flags and their payloads for a user.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to only evaluate locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "flag_keys_to_evaluate", + "description": "A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance.", + "isOptional": true, + "type": "list[str]" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "FlagsAndPayloads" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.get_all_flags_and_payloads('distinct_id_of_your_user')" + } + ] + }, + { + "id": "get_feature_flag", + "title": "get_feature_flag", + "description": "Get multivariate feature flag value for a user.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "The feature flag key.", + "isOptional": true, + "type": "str" + }, + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to only evaluate locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "send_feature_flag_events", + "description": "Whether to send feature flag events.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Union[bool, str, any]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "enabled_variant = posthog.get_feature_flag('flag-key', 'distinct_id_of_your_user')\nif enabled_variant == 'variant-key': # replace 'variant-key' with the key of your variant\n # Do something differently for this user\n # Optional: fetch the payload\n matched_flag_payload = posthog.get_feature_flag_payload('flag-key', 'distinct_id_of_your_user')" + } + ] + }, + { + "id": "get_feature_flag_evaluation_runtime", + "title": "get_feature_flag_evaluation_runtime", + "description": "Return where a locally loaded feature flag is meant to be evaluated.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "The feature flag key.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[FeatureFlagEvaluationRuntime]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import FeatureFlagEvaluationRuntime\n\nruntime = posthog.get_feature_flag_evaluation_runtime(\"my-flag\")\nif runtime is FeatureFlagEvaluationRuntime.SERVER:\n ..." + } + ] + }, + { + "id": "get_feature_flag_keys_by_evaluation_runtime", + "title": "get_feature_flag_keys_by_evaluation_runtime", + "description": "Return the keys of locally loaded flags that a runtime can evaluate. A flag set to ``FeatureFlagEvaluationRuntime.ALL`` suits either runtime, so it is returned for ``CLIENT`` and for ``SERVER``, and asking for ``ALL`` returns every loaded flag. Use this to decide which flags to hand to a browser when a backend serves flags to its own frontend.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "evaluation_runtime", + "description": "The runtime to match, as a ``FeatureFlagEvaluationRuntime`` or its string value.", + "isOptional": true, + "type": "FeatureFlagEvaluationRuntime" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "list[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import FeatureFlagEvaluationRuntime\n\nclient_keys = posthog.get_feature_flag_keys_by_evaluation_runtime(\n FeatureFlagEvaluationRuntime.CLIENT\n)" + } + ] + }, + { + "id": "get_feature_flag_payload", + "title": "get_feature_flag_payload", + "description": "Get the payload for a feature flag.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "The feature flag key.", + "isOptional": true, + "type": "str" + }, + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "match_value", + "description": "The specific flag value to get payload for.", + "isOptional": false, + "type": "bool" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to only evaluate locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "send_feature_flag_events", + "description": "Deprecated. Use get_feature_flag() instead if you need events.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[object]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "is_my_flag_enabled = posthog.feature_enabled('flag-key', 'distinct_id_of_your_user')\n\nif is_my_flag_enabled:\n # Do something differently for this user\n # Optional: fetch the payload\n matched_flag_payload = posthog.get_feature_flag_payload('flag-key', 'distinct_id_of_your_user')" + } + ] + }, + { + "id": "get_feature_flag_result", + "title": "get_feature_flag_result", + "description": "Get a FeatureFlagResult object which contains the flag result and payload for a key by evaluating locally or remotely depending on whether local evaluation is enabled and the flag can be locally evaluated. This also captures the `$feature_flag_called` event unless `send_feature_flag_events` is `False`.", + "details": "", + "category": null, + "params": [ + { + "name": "key", + "description": "The feature flag key.", + "isOptional": true, + "type": "str" + }, + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to only evaluate locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "send_feature_flag_events", + "description": "Whether to send feature flag events.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[FeatureFlagResult]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "flag_result = posthog.get_feature_flag_result('flag-key', 'distinct_id_of_your_user')\nif flag_result and flag_result.get_value() == 'variant-key':\n # Do something differently for this user\n # Optional: fetch the payload\n matched_flag_payload = flag_result.payload" + } + ] + }, + { + "id": "get_feature_flags_and_payloads", + "title": "get_feature_flags_and_payloads", + "description": "Get feature flags and payloads for a user.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "flag_keys_to_evaluate", + "description": "A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance.", + "isOptional": true, + "type": "list[str]" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "FlagsAndPayloads" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "result = posthog.get_feature_flags_and_payloads('')" + } + ] + }, + { + "id": "get_feature_payloads", + "title": "get_feature_payloads", + "description": "Get feature flag payloads for a user, preserving valid serialized JSON.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "flag_keys_to_evaluate", + "description": "A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance.", + "isOptional": true, + "type": "list[str]" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "dict[str, Optional[str]]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "payloads = posthog.get_feature_payloads('')" + } + ] + }, + { + "id": "get_feature_variants", + "title": "get_feature_variants", + "description": "Get feature flag variants for a user.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "flag_keys_to_evaluate", + "description": "A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance.", + "isOptional": true, + "type": "list[str]" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "dict[str, Union[bool, str]]" + } + }, + { + "id": "get_flags_decision", + "title": "get_flags_decision", + "description": "Get feature flags decision.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": false, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "flag_keys_to_evaluate", + "description": "A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance.", + "isOptional": true, + "type": "list[str]" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "FlagsResponse" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "decision = posthog.get_flags_decision('user123')" + } + ] + }, + { + "id": "get_remote_config_payload", + "title": "get_remote_config_payload", + "description": "Get the payload for a remote config feature flag.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "The remote config feature flag key.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "get_tags", + "title": "get_tags", + "description": "Get all tags from the current context. Returns: Dict of all tags in the current context.", + "details": "", + "category": "Contexts", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "dict[str, Any]" + } + }, + { + "id": "group_identify", + "title": "group_identify", + "description": "Identify a group and set its properties.", + "details": "", + "category": "Identification", + "params": [ + { + "name": "group_type", + "description": "The type of group (e.g., 'company', 'team'). Required - the call is dropped with a warning if it is missing or empty.", + "isOptional": true, + "type": "str" + }, + { + "name": "group_key", + "description": "The unique identifier for the group. Required - the call is dropped with a warning if it is missing or empty.", + "isOptional": true, + "type": "str" + }, + { + "name": "properties", + "description": "A dictionary of properties to set on the group.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "timestamp", + "description": "The timestamp of the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC.", + "isOptional": false, + "type": "datetime" + }, + { + "name": "uuid", + "description": "A unique identifier for the event. If provided, it must be a valid UUID string or uuid.UUID instance; invalid values are ignored and replaced with a newly generated UUID.", + "isOptional": false, + "type": "str" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this event.", + "isOptional": true, + "type": "bool" + }, + { + "name": "distinct_id", + "description": "The distinct ID of the user performing the action.", + "isOptional": false, + "type": "Number" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.group_identify('company', 'company_id_in_your_db', {\n 'name': 'Awesome Inc.',\n 'employees': 11\n})" + } + ] + }, + { + "id": "identify_context", + "title": "identify_context", + "description": "Identify the current context with a distinct ID.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "distinct_id", + "description": "The distinct ID to associate with the current context and its children.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + } + }, + { + "id": "join", + "title": "join", + "description": "Attempt to process queued events and end the consumer threads. Do not use directly, call `shutdown()` instead. Failed or undrainable events may be dropped and reported through logging or ``on_error``; returning does not guarantee server receipt. Lifecycle cleanup is attempted once, and cleanup failures are logged without retry.", + "details": "", + "category": null, + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.join()" + } + ] + }, + { + "id": "load_feature_flags", + "title": "load_feature_flags", + "description": "Load feature flags for local evaluation.", + "details": "", + "category": "Feature flags", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.load_feature_flags()" + } + ] + }, + { + "id": "new_context", + "title": "new_context", + "description": "Create a new context for managing shared state. Learn more about [contexts](/docs/libraries/python#contexts).", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "fresh", + "description": "Whether to create a fresh context that doesn't inherit from parent.", + "isOptional": false, + "type": "bool" + }, + { + "name": "capture_exceptions", + "description": "Whether to automatically capture exceptions in this context. If omitted, defaults to this client's exception autocapture setting.", + "isOptional": true, + "type": "bool" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "with client.new_context():\n client.identify_context('')\n client.capture('event_name')" + } + ] + }, + { + "id": "scoped", + "title": "scoped", + "description": "Decorator that creates a new context for the wrapped function using this client.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "fresh", + "description": "Whether to create a fresh context that doesn't inherit from parent.", + "isOptional": false, + "type": "bool" + }, + { + "name": "capture_exceptions", + "description": "Whether to automatically capture exceptions in this context. If omitted, defaults to this client's exception autocapture setting.", + "isOptional": true, + "type": "bool" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "set", + "title": "set", + "description": "Set properties on a person profile.", + "details": "", + "category": "Identification", + "params": [ + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalSetArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Set with distinct id", + "code": "# Set with distinct id\nposthog.set(distinct_id='user123', properties={'name': 'Max Hedgehog'})" + } + ] + }, + { + "id": "set_context_device_id", + "title": "set_context_device_id", + "description": "Set the device ID for the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "device_id", + "description": "The device ID to associate with the current context and its children.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + } + }, + { + "id": "set_context_session", + "title": "set_context_session", + "description": "Set the session ID for the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "session_id", + "description": "The session ID to associate with the current context and its children.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + } + }, + { + "id": "set_once", + "title": "set_once", + "description": "Set properties on a person profile only if they haven't been set before.", + "details": "", + "category": "Identification", + "params": [ + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalSetArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.set_once(distinct_id='user123', properties={'initial_signup_date': '2024-01-01'})" + } + ] + }, + { + "id": "shutdown", + "title": "shutdown", + "description": "Attempt final delivery and permanently shut down the client. Call this for final process cleanup or when disposing of a client that will not be reused. It closes capture lanes, stops background workers and the feature flag poller, and tears down integrations. Do not call it at the end of each request or serverless invocation when the client is shared; call ``flush()`` before returning instead. A shut-down client cannot be reused; create a new client if needed. Normally this method blocks until queued events have been attempted and cleanup finishes. Failed or undrainable events may be dropped and reported through logging or ``on_error``; returning does not guarantee server receipt. Event draining has no timeout. Queued spans get one final flush with up to 30 seconds waiting for an in-flight flush, then a fresh 30-second drain budget; the last HTTP request is not cut short. Any spans it cannot send are discarded with a warning, as are spans still open. Lifecycle cleanup is attempted once, and cleanup failures are logged without retry. When called directly from an SDK callback such as ``on_error``, shutdown is deferred to avoid blocking the worker that invoked the callback. If the callback must coordinate a blocking shutdown, have it signal an application-owned thread and return before that thread calls shutdown. Do not wait inside the callback for another thread or task that calls a lifecycle method.", + "details": "", + "category": null, + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.shutdown()" + } + ] + }, + { + "id": "start_span", + "title": "start_span", + "description": "Start a span for distributed tracing. Alpha. Returns a span handle. Use it as a context manager to make it the active span for the block and end it on exit (recording a raised exception on the way out); or call ``end()`` yourself for a span that cannot wrap a block. Spans started inside the block nest under it automatically. Always returns a usable handle, even when tracing is off, so calling code never branches.", + "details": "", + "category": "Tracing", + "params": [ + { + "name": "name", + "description": "A low-cardinality operation name, e.g. ``GET /users/:id``. Variable values belong in attributes, not the name.", + "isOptional": true, + "type": "str" + }, + { + "name": "kind", + "description": "``internal`` (default), ``server``, ``client``, ``producer`` or ``consumer``.", + "isOptional": true, + "type": "str" + }, + { + "name": "attributes", + "description": "Initial attributes.", + "isOptional": true, + "type": "Mapping[str, Any]" + }, + { + "name": "parent", + "description": "A span handle, or an inbound W3C ``traceparent`` header value to continue a remote trace. Defaults to the active span. A forked child starts with no active span; pass the parent span to continue a trace across a fork.", + "isOptional": false, + "type": "Span" + }, + { + "name": "tracestate", + "description": "The inbound ``tracestate`` header accompanying a ``traceparent`` string ``parent``; preserved and propagated.", + "isOptional": true, + "type": "str" + }, + { + "name": "start_time", + "description": "A ``datetime`` or epoch seconds, to backdate the span.", + "isOptional": false, + "type": "datetime" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Span" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog = Posthog(\"\", traces={\"service_name\": \"checkout-api\"})\n\nwith posthog.start_span(\"POST /checkout\", parent=request.headers.get(\"traceparent\")) as span:\n span.set_attribute(\"plan\", user.plan)\n with posthog.start_span(\"db.query\", kind=\"client\"):\n ...\n outgoing_headers = {\"traceparent\": span.traceparent()}" + } + ] + }, + { + "id": "tag", + "title": "tag", + "description": "Add a tag to the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "name", + "description": "The tag key.", + "isOptional": true, + "type": "str" + }, + { + "name": "value", + "description": "The tag value.", + "isOptional": true, + "type": "Any" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + } + } + ] + }, + { + "id": "PostHogModule", + "title": "PostHog Module Functions", + "description": "Global functions available in the PostHog module", + "functions": [ + { + "id": "alias", + "title": "alias", + "description": "Associate user behaviour before and after they e.g. register, login, or perform some other identifying action.", + "details": "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?\". Particularly useful for associating user behaviour before and after they e.g. register, login, or perform some other identifying action.", + "category": "Identification", + "params": [ + { + "name": "previous_id", + "description": "The unique ID of the user before", + "isOptional": true, + "type": "Number" + }, + { + "name": "distinct_id", + "description": "The current unique id", + "isOptional": true, + "type": "str" + }, + { + "name": "timestamp", + "description": "Optional timestamp for the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC.", + "isOptional": false, + "type": "datetime" + }, + { + "name": "uuid", + "description": "Optional UUID for the event", + "isOptional": true, + "type": "str" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup", + "isOptional": true, + "type": "bool" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Alias user", + "code": "# Alias user\nfrom posthog import alias\nalias(previous_id='distinct_id', distinct_id='alias_id')" + } + ] + }, + { + "id": "capture", + "title": "capture", + "description": "Capture anything a user does within your system.", + "details": "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. A capture call requires an event name to specify the event. We recommend using [verb] [noun], like `movie played` or `movie updated` to easily identify what your events mean later on. Capture takes a number of optional arguments, which are defined by the `OptionalCaptureArgs` type.", + "category": "Events", + "params": [ + { + "name": "event", + "description": "The event name to specify the event **kwargs: Optional arguments including:", + "isOptional": true, + "type": "str" + }, + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalCaptureArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Context and capture usage", + "code": "# Context and capture usage\nfrom posthog import new_context, identify_context, tag_context, capture\n# Enter a new context (e.g. a request/response cycle, an instance of a background job, etc)\nwith new_context():\n # Associate this context with some user, by distinct_id\n identify_context('some user')\n\n # Capture an event, associated with the context-level distinct ID ('some user')\n capture('movie started')\n\n # Capture an event associated with some other user (overriding the context-level distinct ID)\n capture('movie joined', distinct_id='some-other-user')\n\n # Capture an event with some properties\n capture('movie played', properties={'movie_id': '123', 'category': 'romcom'})\n\n # Capture an event with some properties\n capture('purchase', properties={'product_id': '123', 'category': 'romcom'})\n # Capture an event with some associated group\n capture('purchase', groups={'company': 'id:5'})\n\n # Adding a tag to the current context will cause it to appear on all subsequent events\n tag_context('some-tag', 'some-value')\n\n capture('another-event') # Will be captured with `'some-tag': 'some-value'` in the properties dict" + }, + { + "id": "example_2", + "name": "Set event properties", + "code": "# Set event properties\nfrom posthog import capture\ncapture(\n \"user_signed_up\",\n distinct_id=\"distinct_id_of_the_user\",\n properties={\n \"login_type\": \"email\",\n \"is_free_trial\": \"true\"\n }\n)" + } + ] + }, + { + "id": "capture_ai", + "title": "capture_ai", + "description": "Capture an AI event on the dedicated AI capture endpoint. Beta: the signature is stable; operational limits (per-event size cap, batching, endpoint) may change without notice. Takes the same arguments and returns the same value as `capture()`: the event UUID, or None when the event was not admitted (disabled client, or dropped by `before_send`). The event is delivered on an isolated queue with its own consumer pool and a higher per-event size cap, posting to the dedicated AI ingestion endpoint. The payload is sent as given \u2014 no redaction or truncation is applied here.", + "details": "", + "category": "Events", + "params": [ + { + "name": "event", + "description": "The event name, normally one of the `$ai_*` event names. **kwargs: Same optional arguments as `capture()`.", + "isOptional": true, + "type": "str" + }, + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalCaptureArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import capture_ai\n\nuuid = capture_ai(\n \"$ai_generation\",\n distinct_id=\"user_123\",\n properties={\"$ai_model\": \"gpt-5\"},\n)" + } + ] + }, + { + "id": "capture_exception", + "title": "capture_exception", + "description": "Capture exceptions that happen in your code.", + "details": "Capture exception is idempotent - if it is called twice with the same exception instance, only a occurrence will be tracked in posthog. This is because, generally, contexts will cause exceptions to be captured automatically. However, to ensure you track an exception, if you catch and do not re-raise it, capturing it manually is recommended, unless you are certain it will have crossed a context boundary (e.g. by existing a `with posthog.new_context():` block already). If the passed exception was raised and caught, the captured stack trace will consist of every frame between where the exception was raised and the point at which it is captured (the \"traceback\"). If the passed exception was never raised, e.g. if you call `posthog.capture_exception(ValueError(\"Some Error\"))`, the stack trace captured will be the full stack trace at the moment the exception was captured. Note that heavy use of contexts will lead to truncated stack traces, as the exception will be captured by the context entered most recently, 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. `capture_exception` takes the same set of optional arguments as `capture`.", + "category": "Events", + "params": [ + { + "name": "exception", + "description": "The exception to capture. If not provided, the current exception is captured via `sys.exc_info()` **kwargs: Optional capture arguments including distinct_id, properties, timestamp, uuid, groups, flags, send_feature_flags, and disable_geoip. Overriding reserved exception properties through ``properties`` is deprecated and will stop working in the next major version.", + "isOptional": false, + "type": "BaseException" + }, + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalCaptureArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Capture exception", + "code": "# Capture exception\nfrom posthog import capture_exception\ntry:\n risky_operation()\nexcept Exception as e:\n capture_exception(e)" + } + ] + }, + { + "id": "evaluate_flags", + "title": "evaluate_flags", + "description": "Evaluate all feature flags for a user in a single call and return a :class:`FeatureFlagEvaluations` snapshot. Branch on ``.is_enabled()`` / ``.get_flag()`` and pass the same snapshot to ``capture()`` via the ``flags`` option so events carry the exact flag values the code branched on. Prefer this over repeated ``get_feature_flag()`` calls and over ``capture(send_feature_flags=True)`` \u2014 it consolidates flag evaluation into a single ``/flags`` request per incoming request.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The user's distinct ID. If ``None``, falls back to the context distinct_id. If still unresolvable, returns an empty snapshot.", + "isOptional": false, + "type": "Number" + }, + { + "name": "groups", + "description": "Mapping of group type to group key.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "Person properties to use for evaluation.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "Group properties keyed by group type.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "If ``True``, never fall back to remote evaluation and omit flags that cannot be evaluated locally. The snapshot's ``unresolved_flags`` lists them with a reason.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup.", + "isOptional": true, + "type": "bool" + }, + { + "name": "flag_keys", + "description": "Optional list that scopes local evaluation, the underlying ``/flags`` request, and the returned snapshot. When omitted or ``None``, all flags are evaluated. An empty list returns an empty snapshot without evaluating flags. A requested key absent from loaded local definitions is included in one remote fallback per ``evaluate_flags`` call unless ``only_evaluate_locally`` is ``True``. If the server also does not know the key, it is omitted from the snapshot.", + "isOptional": true, + "type": "list[str]" + }, + { + "name": "device_id", + "description": "Optional device ID override. If not provided, falls back to the context device_id (which may be set via tracing headers). Used by experience-continuity flags to match users across distinct_id changes.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "FeatureFlagEvaluations" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import evaluate_flags, capture\nflags = evaluate_flags(\"user_123\", person_properties={\"plan\": \"enterprise\"})\nif flags.is_enabled(\"new-dashboard\"):\n render_new_dashboard()\ncapture(\"page_viewed\", distinct_id=\"user_123\", flags=flags)" + } + ] + }, + { + "id": "feature_enabled", + "title": "feature_enabled", + "description": "Use feature flags to enable or disable features for users.", + "details": "You can call `posthog.load_feature_flags()` before to make sure you're not doing unexpected requests.", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "The feature flag key", + "isOptional": true, + "type": "str" + }, + { + "name": "distinct_id", + "description": "The user's distinct ID", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "Groups mapping", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "Person properties", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "Group properties", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to evaluate only locally", + "isOptional": false, + "type": "bool" + }, + { + "name": "send_feature_flag_events", + "description": "Whether to send feature flag events", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "Optional device ID override for experience-continuity flags", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[bool]" + }, + "examples": [ + { + "id": "example_1", + "name": "Boolean feature flag", + "code": "# Boolean feature flag\nfrom posthog import feature_enabled, get_feature_flag_payload\nis_my_flag_enabled = feature_enabled('flag-key', 'distinct_id_of_your_user')\nif is_my_flag_enabled:\n matched_flag_payload = get_feature_flag_payload('flag-key', 'distinct_id_of_your_user')" + } + ] + }, + { + "id": "feature_flag_definitions", + "title": "feature_flag_definitions", + "description": "Returns loaded feature flags.", + "details": "Returns loaded feature flags, if any. Helpful for debugging what flag information you have loaded.", + "category": "Feature flags", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import feature_flag_definitions\ndefinitions = feature_flag_definitions()" + } + ] + }, + { + "id": "flush", + "title": "flush", + "description": "Attempt immediate delivery of queued events and ended spans, keeping the global client reusable. Call ``flush()`` before a short-lived request or serverless invocation returns when using the shared module-level client. Use ``shutdown()`` only for final process cleanup or a client that will not be reused. Flush does not stop the background workers or feature flag poller, and does not end open spans. Returning does not guarantee server receipt: pending work can remain after a timeout or span export failure, and failed events may be dropped by consumers. Calls made directly from SDK callbacks such as ``on_error`` are deferred rather than blocking the worker that invoked the callback.", + "details": "", + "category": "Client management", + "params": [ + { + "name": "timeout_seconds", + "description": "Seconds to wait for event queues to drain, shared across event lanes. Defaults to 10 seconds. Pass ``None`` to wait without a time limit; this does not guarantee successful delivery. Queued spans flush concurrently, waiting up to this many seconds for an in-flight flush before starting a fresh drain budget. A lock wait timeout leaves the in-flight flush to finish. At least one span request is attempted after acquiring the lock, even with no budget left; retriable failures are retried after backoff while budget remains, with one last attempt at the deadline. The last span request is not cut short and is bounded by the client's ``timeout`` in seconds, so this is not a strict wall-clock limit for the whole call. With ``None``, a retriable span failure is left for a later flush rather than retried within this call.", + "isOptional": true, + "type": "float" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "import posthog\nposthog.capture('event_name', distinct_id='user_id')\n# Before a serverless invocation returns; keep the global client open.\nposthog.flush(timeout_seconds=10)" + } + ] + }, + { + "id": "get_active_span", + "title": "get_active_span", + "description": "The span that is active in the current context, or ``None``. Alpha. Only entering a span (``with posthog.start_span(...) as span:``) makes it active; a span started manually is not. Use it to propagate the trace to the next service: ``span.traceparent()`` is the header value.", + "details": "", + "category": "Tracing", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[Span]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "span = posthog.get_active_span()\nif span is not None:\n headers[\"traceparent\"] = span.traceparent()" + } + ] + }, + { + "id": "get_all_flags", + "title": "get_all_flags", + "description": "Get all flags for a given user.", + "details": "Flags are key-value pairs where the key is the flag key and the value is the flag variant, or True, or False.", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The user's distinct ID", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "Groups mapping", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "Person properties", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "Group properties", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to evaluate only locally", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "Optional device ID override for experience-continuity flags", + "isOptional": true, + "type": "str" + }, + { + "name": "flag_keys_to_evaluate", + "description": "Optional list of flag keys to evaluate (evaluates all if None)", + "isOptional": true, + "type": "list[str]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[dict[str, Union[bool, str]]]" + }, + "examples": [ + { + "id": "example_1", + "name": "All flags for user", + "code": "# All flags for user\nfrom posthog import get_all_flags\nget_all_flags('distinct_id_of_your_user')" + } + ] + }, + { + "id": "get_all_flags_and_payloads", + "title": "get_all_flags_and_payloads", + "description": "Get all feature flag values and payloads for a user.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The user's distinct ID.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "Mapping of group type to group key.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "Person properties to use for evaluation.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "Group properties keyed by group type.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to evaluate only locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup.", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "Optional device ID override for experience-continuity flags.", + "isOptional": true, + "type": "str" + }, + { + "name": "flag_keys_to_evaluate", + "description": "Optional list of flag keys to evaluate. Evaluates all flags when omitted.", + "isOptional": true, + "type": "list[str]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "FlagsAndPayloads" + } + }, + { + "id": "get_feature_flag", + "title": "get_feature_flag", + "description": "Get feature flag variant for users. Used with experiments.", + "details": "`groups` are a mapping from group type to group key. So, if you have a group type of \"organization\" and a group key of \"5\", you would pass groups={\"organization\": \"5\"}. `group_properties` take the format: { group_type_name: { group_properties } }. So, for example, if you have the group type \"organization\" and the group key \"5\", with the properties name, and employee count, you'll send these as: group_properties={\"organization\": {\"name\": \"PostHog\", \"employees\": 11}}.", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "The feature flag key", + "isOptional": true, + "type": "str" + }, + { + "name": "distinct_id", + "description": "The user's distinct ID", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "Groups mapping from group type to group key", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "Person properties", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "Group properties in format { group_type_name: { group_properties } }", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to evaluate only locally", + "isOptional": false, + "type": "bool" + }, + { + "name": "send_feature_flag_events", + "description": "Whether to send feature flag events", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "Optional device ID override for experience-continuity flags", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Union[bool, str, any]" + }, + "examples": [ + { + "id": "example_1", + "name": "Multivariate feature flag", + "code": "# Multivariate feature flag\nfrom posthog import get_feature_flag, get_feature_flag_payload\nenabled_variant = get_feature_flag('flag-key', 'distinct_id_of_your_user')\nif enabled_variant == 'variant-key':\n matched_flag_payload = get_feature_flag_payload('flag-key', 'distinct_id_of_your_user')" + } + ] + }, + { + "id": "get_feature_flag_evaluation_runtime", + "title": "get_feature_flag_evaluation_runtime", + "description": "Return where a locally loaded feature flag is meant to be evaluated.", + "details": "Reads the `evaluation_runtime` each flag definition carries, so no extra request is made. Returns `None` when local evaluation has not loaded a definition for this key. A definition that carries no runtime reports `FeatureFlagEvaluationRuntime.ALL`, the default PostHog applies.", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[FeatureFlagEvaluationRuntime]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import FeatureFlagEvaluationRuntime, get_feature_flag_evaluation_runtime\nruntime = get_feature_flag_evaluation_runtime(\"my-flag\")" + } + ] + }, + { + "id": "get_feature_flag_keys_by_evaluation_runtime", + "title": "get_feature_flag_keys_by_evaluation_runtime", + "description": "Return the keys of locally loaded flags that a runtime can evaluate.", + "details": "A flag set to `FeatureFlagEvaluationRuntime.ALL` suits either runtime, so it is returned for `CLIENT` and for `SERVER`. Use this to decide which flags to hand to a browser when a backend serves flags to its own frontend.", + "category": "Feature flags", + "params": [ + { + "name": "evaluation_runtime", + "description": "", + "isOptional": true, + "type": "FeatureFlagEvaluationRuntime" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "list[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import FeatureFlagEvaluationRuntime, get_feature_flag_keys_by_evaluation_runtime\nclient_keys = get_feature_flag_keys_by_evaluation_runtime(FeatureFlagEvaluationRuntime.CLIENT)" + } + ] + }, + { + "id": "get_feature_flag_payload", + "title": "get_feature_flag_payload", + "description": "Get the payload associated with a feature flag value. Deprecated for new code. Prefer ``evaluate_flags()`` and ``flags.get_flag_payload(key)`` so flag evaluation happens once per request.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "The feature flag key.", + "isOptional": true, + "type": "str" + }, + { + "name": "distinct_id", + "description": "The user's distinct ID.", + "isOptional": true, + "type": "Number" + }, + { + "name": "match_value", + "description": "Optional flag value to use when selecting a payload.", + "isOptional": false, + "type": "bool" + }, + { + "name": "groups", + "description": "Mapping of group type to group key.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "Person properties to use for evaluation.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "Group properties keyed by group type.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to evaluate only locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "send_feature_flag_events", + "description": "Whether to send a $feature_flag_called event.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup.", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "Optional device ID override for experience-continuity flags.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[object]" + } + }, + { + "id": "get_feature_flag_result", + "title": "get_feature_flag_result", + "description": "Get a FeatureFlagResult object which contains the flag result and payload. This method evaluates a feature flag and returns a FeatureFlagResult object containing: - enabled: Whether the flag is enabled - variant: The variant value if the flag has variants - payload: The payload associated with the flag (automatically deserialized from JSON) - key: The flag key - reason: Why the flag was enabled/disabled", + "details": "", + "category": null, + "params": [ + { + "name": "key", + "description": "The feature flag key.", + "isOptional": true, + "type": "str" + }, + { + "name": "distinct_id", + "description": "The user's distinct ID.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "Mapping of group type to group key.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "Person properties to use for evaluation.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "Group properties keyed by group type.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to evaluate only locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "send_feature_flag_events", + "description": "Whether to send a $feature_flag_called event.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup.", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "Optional device ID override for experience-continuity flags.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[FeatureFlagResult]" + } + }, + { + "id": "get_remote_config_payload", + "title": "get_remote_config_payload", + "description": "Get the payload for a remote config feature flag.", + "details": "", + "category": null, + "params": [ + { + "name": "key", + "description": "The key of the feature flag", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "get_tags", + "title": "get_tags", + "description": "Get all tags from the current context. Returns: Dict of all tags in the current context", + "details": "", + "category": "Contexts", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "dict[str, Any]" + } + }, + { + "id": "group_identify", + "title": "group_identify", + "description": "Set properties on a group.", + "details": "", + "category": "Identification", + "params": [ + { + "name": "group_type", + "description": "Type of your group. Required - the call is dropped with a warning if it is missing or empty.", + "isOptional": true, + "type": "str" + }, + { + "name": "group_key", + "description": "Unique identifier of the group. Required - the call is dropped with a warning if it is missing or empty.", + "isOptional": true, + "type": "str" + }, + { + "name": "properties", + "description": "Properties to set on the group", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "timestamp", + "description": "Optional timestamp for the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC.", + "isOptional": false, + "type": "datetime" + }, + { + "name": "uuid", + "description": "Optional UUID for the event", + "isOptional": true, + "type": "str" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup", + "isOptional": true, + "type": "bool" + }, + { + "name": "distinct_id", + "description": "Optional distinct ID of the user performing the action", + "isOptional": false, + "type": "Number" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Group identify", + "code": "# Group identify\nfrom posthog import group_identify\ngroup_identify('company', 'company_id_in_your_db', {\n 'name': 'Awesome Inc.',\n 'employees': 11\n})" + } + ] + }, + { + "id": "identify_context", + "title": "identify_context", + "description": "Identify the current context with a distinct ID.", + "details": "", + "category": "Identification", + "params": [ + { + "name": "distinct_id", + "description": "The distinct ID to associate with the current context and its children", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import identify_context\nidentify_context(\"user_123\")" + } + ] + }, + { + "id": "join", + "title": "join", + "description": "Attempt to process queued events and stop the client's background workers. Use `shutdown()` directly in most cases. Failed or undrainable events may be dropped and reported through logging or ``on_error``; returning does not guarantee server receipt. Lifecycle cleanup is attempted once, and cleanup failures are logged without retry.", + "details": "", + "category": "Client management", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import join\njoin()" + } + ] + }, + { + "id": "load_feature_flags", + "title": "load_feature_flags", + "description": "Load feature flag definitions from PostHog.", + "details": "", + "category": "Feature flags", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import load_feature_flags\nload_feature_flags()" + } + ] + }, + { + "id": "new_context", + "title": "new_context", + "description": "Create a new context scope that will be active for the duration of the with block.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "fresh", + "description": "Whether to start with a fresh context (default: False)", + "isOptional": false, + "type": "bool" + }, + { + "name": "capture_exceptions", + "description": "Whether to capture exceptions raised within the context. If omitted, defaults to the relevant client's exception autocapture setting.", + "isOptional": true, + "type": "bool" + }, + { + "name": "client", + "description": "Optional Posthog client instance to use for this context (default: None)", + "isOptional": true, + "type": "Client" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import new_context, tag, capture\nwith new_context():\n tag(\"request_id\", \"123\")\n capture(\"event_name\", properties={\"property\": \"value\"})" + } + ] + }, + { + "id": "scoped", + "title": "scoped", + "description": "Decorator that creates a new context for the function.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "fresh", + "description": "Whether to start with a fresh context (default: False)", + "isOptional": false, + "type": "bool" + }, + { + "name": "capture_exceptions", + "description": "Whether to capture and track exceptions with posthog error tracking. If omitted, defaults to the global exception autocapture setting.", + "isOptional": true, + "type": "bool" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import scoped, tag, capture\n@scoped()\ndef process_payment(payment_id):\n tag(\"payment_id\", payment_id)\n capture(\"payment_started\")" + } + ] + }, + { + "id": "set", + "title": "set", + "description": "Set properties on a user record.", + "details": "This will overwrite previous people property values. Generally operates similar to `capture`, with distinct_id being an optional argument, defaulting to the current context's distinct ID. If there is no context-level distinct ID, and no override distinct_id is passed, this function will do nothing. Context tags are folded into $set properties, so tagging the current context and then calling `set` will cause those tags to be set on the user (unlike capture, which causes them to just be set on the event).", + "category": "Identification", + "params": [ + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalSetArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Set person properties", + "code": "# Set person properties\nfrom posthog import set\nset(distinct_id='distinct_id', properties={'name': 'Max Hedgehog'})" + } + ] + }, + { + "id": "set_capture_exception_code_variables_context", + "title": "set_capture_exception_code_variables_context", + "description": "Override code-variable capture for exceptions in the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "enabled", + "description": "Whether exceptions captured in this context should include local variable values from stack frames.", + "isOptional": true, + "type": "bool" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "set_code_variables_detect_secrets_context", + "title": "set_code_variables_detect_secrets_context", + "description": "Whether to apply entropy-based secret detection as a last-resort redaction of high-entropy values (API keys, tokens, strong passwords) in captured code variables for the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "enabled", + "description": "", + "isOptional": true, + "type": "bool" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "set_code_variables_ignore_patterns_context", + "title": "set_code_variables_ignore_patterns_context", + "description": "Override code-variable ignore patterns for exceptions in the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "ignore_patterns", + "description": "Variable-name patterns that should be omitted entirely when code variables are captured.", + "isOptional": true, + "type": "list[str]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "set_code_variables_mask_patterns_context", + "title": "set_code_variables_mask_patterns_context", + "description": "Override code-variable mask patterns for exceptions in the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "mask_patterns", + "description": "Variable-name patterns whose values should be replaced with ``***`` when code variables are captured.", + "isOptional": true, + "type": "list[str]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "set_code_variables_mask_url_credentials_context", + "title": "set_code_variables_mask_url_credentials_context", + "description": "Whether to scrub credentials embedded in URLs/DSNs (e.g. user:pass@host) from captured code variables for the current context.", + "details": "", + "category": null, + "params": [ + { + "name": "enabled", + "description": "", + "isOptional": true, + "type": "bool" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "set_context_device_id", + "title": "set_context_device_id", + "description": "Set the device ID for the current context, associating all feature flag requests in this or child contexts with the given device ID.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "device_id", + "description": "The device ID to associate with the current context and its children", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import set_context_device_id\nset_context_device_id(\"device_123\")" + } + ] + }, + { + "id": "set_context_session", + "title": "set_context_session", + "description": "Set the session ID for the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "session_id", + "description": "The session ID to associate with the current context and its children", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import set_context_session\nset_context_session(\"session_123\")" + } + ] + }, + { + "id": "set_once", + "title": "set_once", + "description": "Set properties on a user record, only if they do not yet exist.", + "details": "This will not overwrite previous people property values, unlike `set`. Otherwise, operates in an identical manner to `set`.", + "category": "Identification", + "params": [ + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalSetArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Set property once", + "code": "# Set property once\nfrom posthog import set_once\nset_once(distinct_id='distinct_id', properties={'initial_url': '/blog'})" + } + ] + }, + { + "id": "setup", + "title": "setup", + "description": "Create or return the global PostHog client configured by module settings. Most applications should either instantiate ``Posthog`` directly or set ``posthog.api_key``/other module settings before calling top-level helpers. ``setup()`` is called automatically by global APIs such as ``capture()``. Returns: The global ``Client`` instance. If both ``api_key`` and ``project_api_key`` are missing or blank, the client is disabled and module-level calls become no-ops.", + "details": "", + "category": "Initialization", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Client" + } + }, + { + "id": "shutdown", + "title": "shutdown", + "description": "Attempt final delivery and permanently shut down the global client. Call this for final process cleanup, not at the end of each request or serverless invocation that shares the module-level client. Use ``flush()`` before returning from those invocations instead. Shutdown closes capture lanes, stops background workers and the feature flag poller, and tears down integrations. The global client is not recreated automatically after shutdown. This normally blocks until queued events have been attempted and cleanup finishes. Failed or undrainable events may be dropped and reported through logging or ``on_error``; returning does not guarantee server receipt. Event draining has no timeout. Queued spans get a final flush with up to 30 seconds waiting for an in-flight flush, then a fresh 30-second drain budget; the last HTTP request is not cut short. Unsent queued spans and still-open spans are discarded with a warning. Lifecycle cleanup is attempted once, and cleanup failures are logged without retry. Calls made directly from SDK callbacks such as ``on_error`` are deferred to avoid deadlocking the worker. If blocking completion is required, signal an application-owned thread, return from the callback, and call ``shutdown()`` from that thread. Do not wait inside a callback for another thread or task calling a lifecycle method.", + "details": "", + "category": "Client management", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import shutdown\nshutdown()" + } + ] + }, + { + "id": "start_span", + "title": "start_span", + "description": "Start a span for distributed tracing. Alpha. Returns a span handle. Use it as a context manager to make it the active span for the block and end it on exit (recording a raised exception on the way out); or call ``end()`` yourself for a span that cannot wrap a block. Spans started inside the block nest under it automatically. Always returns a usable handle, even when tracing is off, so calling code never branches.", + "details": "", + "category": "Tracing", + "params": [ + { + "name": "name", + "description": "A low-cardinality operation name, e.g. ``GET /users/:id``. Variable values belong in attributes, not the name.", + "isOptional": true, + "type": "str" + }, + { + "name": "kind", + "description": "``internal`` (default), ``server``, ``client``, ``producer`` or ``consumer``.", + "isOptional": true, + "type": "str" + }, + { + "name": "attributes", + "description": "Initial attributes.", + "isOptional": true, + "type": "Mapping[str, Any]" + }, + { + "name": "parent", + "description": "A span handle, or an inbound W3C ``traceparent`` header value to continue a remote trace. Defaults to the active span.", + "isOptional": false, + "type": "Span" + }, + { + "name": "tracestate", + "description": "The inbound ``tracestate`` header accompanying a ``traceparent`` string ``parent``; preserved and propagated.", + "isOptional": true, + "type": "str" + }, + { + "name": "start_time", + "description": "A ``datetime`` or epoch seconds, to backdate the span.", + "isOptional": false, + "type": "datetime" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Span" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "import posthog\nposthog.traces = {\"service_name\": \"checkout-api\"}\n\nwith posthog.start_span(\"POST /checkout\", parent=request.headers.get(\"traceparent\")) as span:\n span.set_attribute(\"plan\", user.plan)\n with posthog.start_span(\"db.query\", kind=\"client\"):\n ...\n outgoing_headers = {\"traceparent\": span.traceparent()}" + } + ] + }, + { + "id": "tag", + "title": "tag", + "description": "Add a tag to the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "name", + "description": "The tag key", + "isOptional": true, + "type": "str" + }, + { + "name": "value", + "description": "The tag value", + "isOptional": true, + "type": "Any" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import tag\ntag(\"user_id\", \"123\")" + } + ] + } + ] + } + ], + "categories": [ + "Initialization", + "Identification", + "Capture", + "Error Tracking", + "Feature flags", + "Tracing", + "Contexts", + "Events", + "Client management" + ] +} \ No newline at end of file diff --git a/references/posthog-python-references-latest.json b/references/posthog-python-references-latest.json index cecc1969..841a5b2d 100644 --- a/references/posthog-python-references-latest.json +++ b/references/posthog-python-references-latest.json @@ -2,7 +2,7 @@ "id": "posthog-python", "hogRef": "0.3", "info": { - "version": "7.80.0", + "version": "7.80.1", "id": "posthog-python", "title": "PostHog Python SDK", "description": "Integrate PostHog into any python application.", From a26b96ad86f73fa30657b73f55c2b953e8859881 Mon Sep 17 00:00:00 2001 From: alexcastgonz <82875882+pcbeingused333@users.noreply.github.com> Date: Thu, 29 Oct 2026 07:51:52 -0400 Subject: [PATCH 44/60] fix(ai): capture an $ai_embedding event when an OpenAI embeddings call fails (#1143) Co-authored-by: Claude Sonnet 5.5 (cherry picked from commit ad1b1cb1e2052621d979cfdccb4870d6b4b31613) --- .sampo/changesets/openai-embeddings-error.md | 5 ++ posthog/ai/openai/_embeddings.py | 19 +++++++- posthog/ai/openai/openai.py | 18 ++++++- posthog/ai/openai/openai_async.py | 18 ++++++- posthog/test/ai/openai/test_openai.py | 50 ++++++++++++++++++++ 5 files changed, 106 insertions(+), 4 deletions(-) create mode 100644 .sampo/changesets/openai-embeddings-error.md diff --git a/.sampo/changesets/openai-embeddings-error.md b/.sampo/changesets/openai-embeddings-error.md new file mode 100644 index 00000000..678639cc --- /dev/null +++ b/.sampo/changesets/openai-embeddings-error.md @@ -0,0 +1,5 @@ +--- +pypi/posthog: patch +--- + +Capture an `$ai_embedding` event with `$ai_is_error` and `$ai_error` when an OpenAI `embeddings.create()` call fails, in the sync and async wrappers. Before, a failed embeddings call left no event. diff --git a/posthog/ai/openai/_embeddings.py b/posthog/ai/openai/_embeddings.py index 8c6565d3..aefecbfa 100644 --- a/posthog/ai/openai/_embeddings.py +++ b/posthog/ai/openai/_embeddings.py @@ -1,6 +1,12 @@ from typing import Any, Dict, Optional -from ..utils import _capture_ai_event, finalize_ai_content, with_privacy_mode +from ..utils import ( + _capture_ai_event, + _format_ai_error, + _get_http_status, + finalize_ai_content, + with_privacy_mode, +) def _capture_embedding_event( @@ -15,6 +21,7 @@ def _capture_embedding_event( properties: Optional[Dict[str, Any]], privacy_mode: bool, groups: Optional[Dict[str, Any]], + error: Optional[BaseException] = None, ) -> None: """Build and capture telemetry shared by sync and async embedding wrappers.""" usage = getattr(response, "usage", None) @@ -28,7 +35,7 @@ def _capture_embedding_event( privacy_mode, finalize_ai_content(request_kwargs.get("input"), posthog_client), ), - "$ai_http_status": 200, + "$ai_http_status": _get_http_status(error) if error is not None else 200, # Omitted when the provider never reported a count: absent means # unknown, 0 is a report of nothing. **({"$ai_input_tokens": input_tokens} if input_tokens is not None else {}), @@ -36,6 +43,14 @@ def _capture_embedding_event( "$ai_trace_id": trace_id, "$ai_base_url": str(base_url), **(properties or {}), + **( + { + "$ai_is_error": True, + "$ai_error": _format_ai_error(posthog_client, privacy_mode, error), + } + if error is not None + else {} + ), } if callable(getattr(posthog_client, "capture_ai", None)): diff --git a/posthog/ai/openai/openai.py b/posthog/ai/openai/openai.py index 674908f2..8b7a8abc 100644 --- a/posthog/ai/openai/openai.py +++ b/posthog/ai/openai/openai.py @@ -498,7 +498,23 @@ def create( ) start_time = time.time() - response = self._original.create(**kwargs) + try: + response = self._original.create(**kwargs) + except Exception as exc: + _capture_embedding_event( + posthog_client=self._client._ph_client, + base_url=self._client.base_url, + response=None, + request_kwargs=kwargs, + latency=time.time() - start_time, + distinct_id=posthog_distinct_id, + trace_id=posthog_trace_id, + properties=posthog_properties, + privacy_mode=posthog_privacy_mode, + groups=posthog_groups, + error=exc, + ) + raise end_time = time.time() _capture_embedding_event( diff --git a/posthog/ai/openai/openai_async.py b/posthog/ai/openai/openai_async.py index 3d6dcbf2..f48fc750 100644 --- a/posthog/ai/openai/openai_async.py +++ b/posthog/ai/openai/openai_async.py @@ -502,7 +502,23 @@ async def create( ) start_time = time.time() - response = await self._original.create(**kwargs) + try: + response = await self._original.create(**kwargs) + except Exception as exc: + _capture_embedding_event( + posthog_client=self._client._ph_client, + base_url=self._client.base_url, + response=None, + request_kwargs=kwargs, + latency=time.time() - start_time, + distinct_id=posthog_distinct_id, + trace_id=posthog_trace_id, + properties=posthog_properties, + privacy_mode=posthog_privacy_mode, + groups=posthog_groups, + error=exc, + ) + raise end_time = time.time() _capture_embedding_event( diff --git a/posthog/test/ai/openai/test_openai.py b/posthog/test/ai/openai/test_openai.py index b1a6d6a2..396183cb 100644 --- a/posthog/test/ai/openai/test_openai.py +++ b/posthog/test/ai/openai/test_openai.py @@ -488,6 +488,33 @@ def mock_create(self, **kwargs): assert stream_options == {"foo": "bar"} +def test_embeddings_error(mock_client): + with patch( + "openai.resources.embeddings.Embeddings.create", + side_effect=Exception("Test error"), + ): + client = OpenAI(api_key="test-key", posthog_client=mock_client) + with pytest.raises(Exception, match="Test error"): + client.embeddings.create( + model="text-embedding-3-small", + input="Hello world", + posthog_distinct_id="test-id", + ) + + assert mock_client.capture_ai.call_count == 1 + + call_args = mock_client.capture_ai.call_args[1] + props = call_args["properties"] + assert call_args["distinct_id"] == "test-id" + assert call_args["event"] == "$ai_embedding" + assert props["$ai_model"] == "text-embedding-3-small" + assert props["$ai_input"] == "Hello world" + assert props["$ai_is_error"] is True + assert props["$ai_error"] == "Test error" + assert props["$ai_http_status"] == 0 + assert "$ai_input_tokens" not in props + + def test_groups(mock_client, mock_openai_response): with patch( "openai.resources.chat.completions.Completions.create", @@ -1468,6 +1495,29 @@ async def chunk_iterable(): assert isinstance(props["$ai_latency"], float) +@pytest.mark.asyncio +async def test_async_embeddings_error(mock_client): + mock_create = AsyncMock(side_effect=Exception("Test error")) + + with patch("openai.resources.embeddings.AsyncEmbeddings.create", new=mock_create): + client = AsyncOpenAI(api_key="test-key", posthog_client=mock_client) + with pytest.raises(Exception, match="Test error"): + await client.embeddings.create( + model="text-embedding-3-small", + input="Hello world", + posthog_distinct_id="test-id", + ) + + assert mock_client.capture_ai.call_count == 1 + + call_args = mock_client.capture_ai.call_args[1] + props = call_args["properties"] + assert call_args["event"] == "$ai_embedding" + assert props["$ai_is_error"] is True + assert props["$ai_error"] == "Test error" + assert props["$ai_http_status"] == 0 + + @pytest.mark.asyncio @pytest.mark.parametrize("stream_options", [None, NOT_GIVEN, {"foo": "bar"}]) async def test_async_streaming_stream_options_are_not_mutated( From 76023259213e04636d55204750d99ac796aa043f Mon Sep 17 00:00:00 2001 From: "releaser-posthog-python[bot]" <262414804+releaser-posthog-python[bot]@users.noreply.github.com> Date: Thu, 29 Oct 2026 11:53:09 +0000 Subject: [PATCH 45/60] chore: Release v7.80.2 [skip ci] (cherry picked from commit 9a9d8067157f922c55503b53a68e9a220d568351) --- .sampo/changesets/openai-embeddings-error.md | 5 ----- openfeature-provider/CHANGELOG.md | 6 ++++++ openfeature-provider/pyproject.toml | 2 +- posthog/CHANGELOG.md | 6 ++++++ posthog/version.py | 2 +- pyproject.toml | 2 +- uv.lock | 4 ++-- 7 files changed, 17 insertions(+), 10 deletions(-) delete mode 100644 .sampo/changesets/openai-embeddings-error.md diff --git a/.sampo/changesets/openai-embeddings-error.md b/.sampo/changesets/openai-embeddings-error.md deleted file mode 100644 index 678639cc..00000000 --- a/.sampo/changesets/openai-embeddings-error.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -pypi/posthog: patch ---- - -Capture an `$ai_embedding` event with `$ai_is_error` and `$ai_error` when an OpenAI `embeddings.create()` call fails, in the sync and async wrappers. Before, a failed embeddings call left no event. diff --git a/openfeature-provider/CHANGELOG.md b/openfeature-provider/CHANGELOG.md index b80b9e7c..5c089f07 100644 --- a/openfeature-provider/CHANGELOG.md +++ b/openfeature-provider/CHANGELOG.md @@ -4,6 +4,12 @@ All notable changes to `openfeature-provider-posthog` are documented here. This file is maintained by [Sampo](https://github.com/bruits/sampo) from changesets in `.sampo/changesets/` that target `pypi/openfeature-provider-posthog`. +## 0.1.133 — 2026-10-29 + +### Patch changes + +- Updated dependencies: posthog@7.80.2 + ## 0.1.132 — 2026-10-29 ### Patch changes diff --git a/openfeature-provider/pyproject.toml b/openfeature-provider/pyproject.toml index 87702610..933e1007 100644 --- a/openfeature-provider/pyproject.toml +++ b/openfeature-provider/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "openfeature-provider-posthog" -version = "0.1.132" +version = "0.1.133" description = "Official PostHog provider for the OpenFeature Python SDK." readme = "README.md" authors = [{ name = "PostHog", email = "engineering@posthog.com" }] diff --git a/posthog/CHANGELOG.md b/posthog/CHANGELOG.md index 72393f86..00de658b 100644 --- a/posthog/CHANGELOG.md +++ b/posthog/CHANGELOG.md @@ -1,5 +1,11 @@ # posthog +## 7.80.2 — 2026-10-29 + +### Patch changes + +- [ad1b1cb](https://github.com/posthog/posthog-python/commit/ad1b1cb1e2052621d979cfdccb4870d6b4b31613) Capture an `$ai_embedding` event with `$ai_is_error` and `$ai_error` when an OpenAI `embeddings.create()` call fails, in the sync and async wrappers. Before, a failed embeddings call left no event. — Thanks @pcbeingused333! + ## 7.80.1 — 2026-10-29 ### Patch changes diff --git a/posthog/version.py b/posthog/version.py index f32b8b91..e4c192ab 100644 --- a/posthog/version.py +++ b/posthog/version.py @@ -1 +1 @@ -VERSION = "7.80.1" +VERSION = "7.80.2" diff --git a/pyproject.toml b/pyproject.toml index 6fc12f13..1d54ec8a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "posthog" -version = "7.80.1" +version = "7.80.2" description = "Integrate PostHog into any python application." authors = [{ name = "PostHog", email = "engineering@posthog.com" }] maintainers = [{ name = "PostHog", email = "engineering@posthog.com" }] diff --git a/uv.lock b/uv.lock index edfef025..06c5e3e0 100644 --- a/uv.lock +++ b/uv.lock @@ -2656,7 +2656,7 @@ wheels = [ [[package]] name = "openfeature-provider-posthog" -version = "0.1.132" +version = "0.1.133" source = { editable = "openfeature-provider" } dependencies = [ { name = "openfeature-sdk" }, @@ -3193,7 +3193,7 @@ wheels = [ [[package]] name = "posthog" -version = "7.80.1" +version = "7.80.2" source = { editable = "." } dependencies = [ { name = "distro" }, From f0963a0ee8ebe7c4c548e6509682bc3b50b1300d Mon Sep 17 00:00:00 2001 From: "releaser-posthog-python[bot]" <262414804+releaser-posthog-python[bot]@users.noreply.github.com> Date: Thu, 29 Oct 2026 11:53:16 +0000 Subject: [PATCH 46/60] Update generated references (cherry picked from commit ad79bd03c268eb02f4b17ab8fc21998a1e3bf2fe) --- .../posthog-python-references-7.80.2.json | 3548 +++++++++++++++++ .../posthog-python-references-latest.json | 2 +- 2 files changed, 3549 insertions(+), 1 deletion(-) create mode 100644 references/posthog-python-references-7.80.2.json diff --git a/references/posthog-python-references-7.80.2.json b/references/posthog-python-references-7.80.2.json new file mode 100644 index 00000000..d8cd4506 --- /dev/null +++ b/references/posthog-python-references-7.80.2.json @@ -0,0 +1,3548 @@ +{ + "id": "posthog-python", + "hogRef": "0.3", + "info": { + "version": "7.80.2", + "id": "posthog-python", + "title": "PostHog Python SDK", + "description": "Integrate PostHog into any python application.", + "slugPrefix": "posthog-python", + "specUrl": "https://github.com/PostHog/posthog-python" + }, + "types": [ + { + "id": "FeatureFlag", + "name": "FeatureFlag", + "path": "posthog.types.FeatureFlag", + "properties": [ + { + "name": "key", + "type": "str", + "description": "Field: key" + }, + { + "name": "enabled", + "type": "bool", + "description": "Field: enabled" + }, + { + "name": "variant", + "type": "Optional[str]", + "description": "Field: variant" + }, + { + "name": "reason", + "type": "Optional[FlagReason]", + "description": "Field: reason" + }, + { + "name": "metadata", + "type": "Union[FlagMetadata, LegacyFlagMetadata]", + "description": "Field: metadata" + } + ], + "example": "" + }, + { + "id": "FeatureFlagResult", + "name": "FeatureFlagResult", + "path": "posthog.types.FeatureFlagResult", + "properties": [ + { + "name": "key", + "type": "str", + "description": "Field: key" + }, + { + "name": "enabled", + "type": "bool", + "description": "Field: enabled" + }, + { + "name": "variant", + "type": "Optional[str]", + "description": "Field: variant" + }, + { + "name": "payload", + "type": "Optional[Any]", + "description": "Field: payload" + }, + { + "name": "reason", + "type": "Optional[str]", + "description": "Field: reason" + } + ], + "example": "" + }, + { + "id": "FlagMetadata", + "name": "FlagMetadata", + "path": "posthog.types.FlagMetadata", + "properties": [ + { + "name": "id", + "type": "int", + "description": "Field: id" + }, + { + "name": "payload", + "type": "Optional[str]", + "description": "Field: payload" + }, + { + "name": "version", + "type": "int", + "description": "Field: version" + }, + { + "name": "description", + "type": "str", + "description": "Field: description" + }, + { + "name": "has_experiment", + "type": "Optional[bool]", + "description": "Field: has_experiment" + } + ], + "example": "" + }, + { + "id": "FlagReason", + "name": "FlagReason", + "path": "posthog.types.FlagReason", + "properties": [ + { + "name": "code", + "type": "str", + "description": "Field: code" + }, + { + "name": "condition_index", + "type": "Optional[int]", + "description": "Field: condition_index" + }, + { + "name": "description", + "type": "str", + "description": "Field: description" + } + ], + "example": "" + }, + { + "id": "FlagsAndPayloads", + "name": "FlagsAndPayloads", + "path": "posthog.types.FlagsAndPayloads", + "properties": [ + { + "name": "featureFlags", + "type": "Optional[dict[str, Union[bool, str]]]", + "description": "Field: featureFlags" + }, + { + "name": "featureFlagPayloads", + "type": "Optional[dict[str, Any]]", + "description": "Field: featureFlagPayloads" + } + ], + "example": "" + }, + { + "id": "FlagsResponse", + "name": "FlagsResponse", + "path": "posthog.types.FlagsResponse", + "properties": [ + { + "name": "flags", + "type": "dict[str, FeatureFlag]", + "description": "Field: flags" + }, + { + "name": "errorsWhileComputingFlags", + "type": "bool", + "description": "Field: errorsWhileComputingFlags" + }, + { + "name": "requestId", + "type": "str", + "description": "Field: requestId" + }, + { + "name": "quotaLimit", + "type": "Optional[list[str]]", + "description": "Field: quotaLimit" + }, + { + "name": "evaluatedAt", + "type": "Optional[int]", + "description": "Field: evaluatedAt" + }, + { + "name": "minimalFlagCalledEvents", + "type": "bool", + "description": "Field: minimalFlagCalledEvents" + } + ], + "example": "" + }, + { + "id": "LegacyFlagMetadata", + "name": "LegacyFlagMetadata", + "path": "posthog.types.LegacyFlagMetadata", + "properties": [ + { + "name": "payload", + "type": "Any", + "description": "Field: payload" + } + ], + "example": "" + }, + { + "id": "SendFeatureFlagsOptions", + "name": "SendFeatureFlagsOptions", + "path": "posthog.types.SendFeatureFlagsOptions", + "properties": [ + { + "name": "should_send", + "type": "bool", + "description": "Field: should_send" + }, + { + "name": "only_evaluate_locally", + "type": "Optional[bool]", + "description": "Field: only_evaluate_locally" + }, + { + "name": "person_properties", + "type": "Optional[dict[str, Any]]", + "description": "Field: person_properties" + }, + { + "name": "group_properties", + "type": "Optional[dict[str, dict[str, Any]]]", + "description": "Field: group_properties" + }, + { + "name": "flag_keys_filter", + "type": "Optional[list[str]]", + "description": "Field: flag_keys_filter" + } + ], + "example": "" + }, + { + "id": "OptionalCaptureArgs", + "name": "OptionalCaptureArgs", + "path": "posthog.args.OptionalCaptureArgs", + "properties": [ + { + "name": "distinct_id", + "type": "NotRequired[Union[Number, str, UUID, int, any]]", + "description": "Field: distinct_id" + }, + { + "name": "properties", + "type": "NotRequired[Optional[dict[str, Any]]]", + "description": "Field: properties" + }, + { + "name": "timestamp", + "type": "NotRequired[Union[datetime, str, any]]", + "description": "Field: timestamp" + }, + { + "name": "uuid", + "type": "NotRequired[Union[str, UUID, any]]", + "description": "Field: uuid" + }, + { + "name": "groups", + "type": "NotRequired[Optional[dict[str, str]]]", + "description": "Field: groups" + }, + { + "name": "flags", + "type": "NotRequired[Optional[ForwardRef('FeatureFlagEvaluations')]]", + "description": "Field: flags" + }, + { + "name": "send_feature_flags", + "type": "NotRequired[Union[bool, SendFeatureFlagsOptions, any]]", + "description": "Field: send_feature_flags" + }, + { + "name": "disable_geoip", + "type": "NotRequired[Optional[bool]]", + "description": "Field: disable_geoip" + }, + { + "name": "_property_allowlist", + "type": "NotRequired[Optional[frozenset[str]]]", + "description": "Field: _property_allowlist" + } + ], + "example": "" + }, + { + "id": "OptionalSetArgs", + "name": "OptionalSetArgs", + "path": "posthog.args.OptionalSetArgs", + "properties": [ + { + "name": "distinct_id", + "type": "NotRequired[Union[Number, str, UUID, int, any]]", + "description": "Field: distinct_id" + }, + { + "name": "properties", + "type": "NotRequired[Optional[dict[str, Any]]]", + "description": "Field: properties" + }, + { + "name": "timestamp", + "type": "NotRequired[Union[datetime, str, any]]", + "description": "Field: timestamp" + }, + { + "name": "uuid", + "type": "NotRequired[Union[str, UUID, any]]", + "description": "Field: uuid" + }, + { + "name": "disable_geoip", + "type": "NotRequired[Optional[bool]]", + "description": "Field: disable_geoip" + } + ], + "example": "" + }, + { + "id": "SendFeatureFlagsOptions", + "name": "SendFeatureFlagsOptions", + "path": "posthog.types.SendFeatureFlagsOptions", + "properties": [ + { + "name": "should_send", + "type": "bool", + "description": "Field: should_send" + }, + { + "name": "only_evaluate_locally", + "type": "Optional[bool]", + "description": "Field: only_evaluate_locally" + }, + { + "name": "person_properties", + "type": "Optional[dict[str, Any]]", + "description": "Field: person_properties" + }, + { + "name": "group_properties", + "type": "Optional[dict[str, dict[str, Any]]]", + "description": "Field: group_properties" + }, + { + "name": "flag_keys_filter", + "type": "Optional[list[str]]", + "description": "Field: flag_keys_filter" + } + ], + "example": "" + } + ], + "classes": [ + { + "id": "PostHog", + "title": "PostHog", + "description": "This is the SDK reference for the PostHog Python SDK. You can learn more about example usage in the [Python SDK documentation](/docs/libraries/python). You can also follow [Flask](/docs/libraries/flask) and [Django](/docs/libraries/django) guides to integrate PostHog into your project. For long-running applications, create one client during application startup and reuse it for the lifetime of the process. This keeps background queues predictable and makes shutdown flushing straightforward. Multiple clients are still supported for intentional multi-project or multi-host setups.", + "functions": [ + { + "id": "__init__", + "title": "Client", + "description": "Initialize a new PostHog client instance.", + "details": "", + "category": "Initialization", + "params": [ + { + "name": "project_api_key", + "description": "PostHog project API key/token.", + "isOptional": true, + "type": "str" + }, + { + "name": "host", + "description": "PostHog host. Defaults to the US ingestion endpoint when not set. App hosts such as ``https://us.posthog.com`` are mapped to the corresponding ingestion host.", + "isOptional": false, + "type": "any" + }, + { + "name": "debug", + "description": "Enable verbose SDK logging and re-raise errors from public API methods.", + "isOptional": false, + "type": "bool" + }, + { + "name": "max_queue_size", + "description": "Maximum number of events buffered before upload.", + "isOptional": false, + "type": "int" + }, + { + "name": "send", + "description": "If False, queueing succeeds but events are not sent.", + "isOptional": false, + "type": "bool" + }, + { + "name": "on_error", + "description": "Optional callback invoked by background consumers when an upload fails. Keep it short and non-blocking. Calling lifecycle methods directly is safe and deferred, but do not start another thread or task that calls ``flush()``, ``join()``, or ``shutdown()`` and then wait for it from the callback.", + "isOptional": false, + "type": "any" + }, + { + "name": "flush_at", + "description": "Number of queued events that triggers a batch upload.", + "isOptional": false, + "type": "int" + }, + { + "name": "flush_interval", + "description": "Maximum seconds a background consumer waits before flushing a partial batch.", + "isOptional": false, + "type": "float" + }, + { + "name": "gzip", + "description": "Whether to gzip event upload payloads.", + "isOptional": false, + "type": "bool" + }, + { + "name": "max_retries", + "description": "Number of upload retries. Values below 0 are treated as 0.", + "isOptional": false, + "type": "int" + }, + { + "name": "sync_mode", + "description": "If True, send each event synchronously instead of using background worker threads. This blocks the calling thread; in asyncio applications such as FastAPI, use ``AsyncPosthog`` instead.", + "isOptional": false, + "type": "bool" + }, + { + "name": "timeout", + "description": "HTTP request timeout in seconds for event uploads.", + "isOptional": false, + "type": "float" + }, + { + "name": "thread", + "description": "Number of background consumer threads.", + "isOptional": false, + "type": "int" + }, + { + "name": "poll_interval", + "description": "Seconds between local feature flag definition refreshes.", + "isOptional": false, + "type": "int" + }, + { + "name": "personal_api_key", + "description": "Deprecated alias for ``secret_key``. Still honored for backwards compatibility; prefer ``secret_key``, which also accepts a Project Secret API Key.", + "isOptional": false, + "type": "any" + }, + { + "name": "disabled", + "description": "If True, disable captures and API requests. Useful in tests.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable server-side GeoIP enrichment. Defaults to True.", + "isOptional": false, + "type": "bool" + }, + { + "name": "is_server", + "description": "Whether events are emitted from a server-side runtime. Defaults to True; set to False when using the SDK as a client/CLI so the device OS is attributed to the person normally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "historical_migration", + "description": "Mark events as historical migration imports.", + "isOptional": false, + "type": "bool" + }, + { + "name": "feature_flags_request_timeout_seconds", + "description": "Timeout in seconds for feature flag and remote config requests.", + "isOptional": false, + "type": "float" + }, + { + "name": "feature_flags_request_max_retries", + "description": "Number of retries for feature flag requests after network, transport, or timeout failures. Defaults to 1. Set to 0 to disable retries.", + "isOptional": false, + "type": "int" + }, + { + "name": "super_properties", + "description": "Properties merged into every captured event.", + "isOptional": false, + "type": "any" + }, + { + "name": "enable_exception_autocapture", + "description": "Automatically capture uncaught exceptions.", + "isOptional": false, + "type": "bool" + }, + { + "name": "log_captured_exceptions", + "description": "Also log exceptions captured by error tracking.", + "isOptional": false, + "type": "bool" + }, + { + "name": "project_root", + "description": "Root path used to determine in-app stack frames for captured exceptions. Defaults to the current working directory.", + "isOptional": false, + "type": "any" + }, + { + "name": "privacy_mode", + "description": "For AI observability, capture usage metadata without prompt inputs or outputs.", + "isOptional": false, + "type": "bool" + }, + { + "name": "before_send", + "description": "Optional callback that can modify or drop events before upload. Return ``None`` to drop an event.", + "isOptional": false, + "type": "any" + }, + { + "name": "flag_fallback_cache_url", + "description": "Optional feature flag fallback cache URL, such as ``memory://local/?ttl=300&size=10000`` or a Redis URL.", + "isOptional": false, + "type": "any" + }, + { + "name": "enable_local_evaluation", + "description": "Whether to poll feature flag definitions for local evaluation when a personal API key is configured.", + "isOptional": false, + "type": "bool" + }, + { + "name": "flag_definition_cache_provider", + "description": "Optional external cache provider for sharing feature flag definitions across workers.", + "isOptional": true, + "type": "FlagDefinitionCacheProvider" + }, + { + "name": "capture_exception_code_variables", + "description": "Capture local variable values on exception stack frames.", + "isOptional": false, + "type": "bool" + }, + { + "name": "code_variables_mask_patterns", + "description": "Variable-name patterns to mask when capturing code variables.", + "isOptional": false, + "type": "any" + }, + { + "name": "code_variables_ignore_patterns", + "description": "Variable-name patterns to omit when capturing code variables.", + "isOptional": false, + "type": "any" + }, + { + "name": "code_variables_mask_url_credentials", + "description": "Scrub credentials embedded in URLs/DSNs (e.g. ``user:pass@host``) from captured code variables, regardless of the surrounding variable name. Defaults to True.", + "isOptional": false, + "type": "any" + }, + { + "name": "code_variables_detect_secrets", + "description": "Last-resort entropy-based detection that redacts high-entropy secret-looking values (API keys, tokens, strong passwords) sitting in innocuously-named variables, after the name and URL checks. Skips structured ids (UUIDs, ObjectIds, hashes). Defaults to True.", + "isOptional": false, + "type": "any" + }, + { + "name": "in_app_modules", + "description": "Module/package prefixes treated as in-app frames in captured exceptions.", + "isOptional": false, + "type": "UnionType[list[str], any]" + }, + { + "name": "enable_exception_autocapture_rate_limiting", + "description": "Rate limit autocaptured exceptions client-side with a token bucket per exception type. Disabled by default.", + "isOptional": false, + "type": "bool" + }, + { + "name": "exception_autocapture_bucket_size", + "description": "Maximum burst of autocaptured exceptions allowed per exception type (token bucket size, clamped to 0-100).", + "isOptional": false, + "type": "int" + }, + { + "name": "exception_autocapture_refill_rate", + "description": "Tokens restored per refill interval for each exception type's bucket.", + "isOptional": false, + "type": "int" + }, + { + "name": "exception_autocapture_refill_interval_seconds", + "description": "Seconds between token refills for autocaptured exception rate limiting.", + "isOptional": false, + "type": "int" + }, + { + "name": "capture_mode", + "description": "Capture wire protocol to use. Defaults to ``CaptureMode.V0`` (legacy ``/batch/``). Set ``CaptureMode.V1`` (or pass the string ``\"v1\"``) to opt into ``/i/v1/analytics/events``. When omitted, the ``POSTHOG_CAPTURE_MODE`` env var is consulted, then ``V0``.", + "isOptional": false, + "type": "CaptureMode" + }, + { + "name": "capture_compression", + "description": "Request-body compression for capture-v1 uploads (ignored in V0, which uses ``gzip``). ``CaptureCompression.GZIP`` or ``DEFLATE`` (or the strings ``\"gzip\"``/``\"deflate\"``). When omitted, the ``POSTHOG_CAPTURE_COMPRESSION`` env var is consulted, then the legacy ``gzip`` flag, then no compression.", + "isOptional": false, + "type": "CaptureCompression" + }, + { + "name": "secret_key", + "description": "A Personal API Key or Project Secret API Key, used to authenticate local feature flag evaluation, remote config payloads, and decrypted flag payloads. Example:: posthog.Client(project_api_key, secret_key=\"phx_...\")", + "isOptional": false, + "type": "any" + }, + { + "name": "metrics", + "description": "", + "isOptional": true, + "type": "dict" + }, + { + "name": "enable_full_ai_capture", + "description": "Route PostHog AI wrapper events through the dedicated AI capture endpoint and capture full AI content: skips string truncation and passes media (base64/data URIs) through unredacted. ``privacy_mode`` always wins. Defaults to False.", + "isOptional": false, + "type": "bool" + }, + { + "name": "capture_trace_context", + "description": "When OpenTelemetry is installed and a valid span is active at capture time, add its trace and span IDs as ``$trace_id`` and ``$span_id`` properties to events captured with ``capture()`` and ``capture_ai()``, so they can be correlated with backend traces. Explicit ``$trace_id``/``$span_id`` values passed in ``properties`` win. Exception events (``capture_exception``) always attach these IDs regardless of this setting. Defaults to False.", + "isOptional": false, + "type": "bool" + }, + { + "name": "_use_ai_lane", + "description": "", + "isOptional": false, + "type": "bool" + }, + { + "name": "_enable_multimodal_capture", + "description": "", + "isOptional": false, + "type": "bool" + }, + { + "name": "traces", + "description": "Config dict for distributed tracing: ``service_name``, ``service_version``, ``environment``, ``resource_attributes``, ``flush_interval`` (5 s), ``max_queue_size`` (2048), ``max_export_batch_size`` (512), ``max_live_spans`` (10000), ``max_span_age`` (3600 s), ``max_attributes_per_span`` (128), ``max_events_per_span`` (128), ``max_attribute_value_length`` (8192). ``before_span_send`` is a callable, or a list run in order, that receives each finished span as a dict (``trace_id``, ``span_id`` and ``parent_span_id`` are read-only) and returns it, edited, or ``None`` to drop it; a hook that raises drops the span. Tracing is off until this is provided. Spans export on a background timer even with ``sync_mode``; serverless handlers should call ``flush()`` before returning. Defaults to None.", + "isOptional": true, + "type": "dict" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import Posthog\n\nposthog = Posthog('', host='')" + } + ] + }, + { + "id": "alias", + "title": "alias", + "description": "Create an alias between two distinct IDs.", + "details": "", + "category": "Identification", + "params": [ + { + "name": "previous_id", + "description": "The previous distinct ID. Required - the call is dropped with a warning if it is missing or empty.", + "isOptional": true, + "type": "Number" + }, + { + "name": "distinct_id", + "description": "The new distinct ID to alias to. Falls back to the context distinct ID; the call is dropped with a warning if neither is available.", + "isOptional": true, + "type": "str" + }, + { + "name": "timestamp", + "description": "The timestamp of the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC.", + "isOptional": false, + "type": "datetime" + }, + { + "name": "uuid", + "description": "A unique identifier for the event. If provided, it must be a valid UUID string or uuid.UUID instance; invalid values are ignored and replaced with a newly generated UUID.", + "isOptional": true, + "type": "str" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this event.", + "isOptional": true, + "type": "bool" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.alias(previous_id='distinct_id', distinct_id='alias_id')" + } + ] + }, + { + "id": "capture", + "title": "capture", + "description": "Captures an event manually. [Learn about capture best practices](https://posthog.com/docs/product-analytics/capture-events)", + "details": "", + "category": "Capture", + "params": [ + { + "name": "event", + "description": "The event name to capture.", + "isOptional": true, + "type": "str" + }, + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalCaptureArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Anonymous event", + "code": "# Anonymous event\nposthog.capture('some-anon-event')" + }, + { + "id": "example_2", + "name": "Context usage", + "code": "# Context usage\nfrom posthog import identify_context, new_context\nwith new_context():\n identify_context('distinct_id_of_the_user')\n posthog.capture('user_signed_up')\n posthog.capture('user_logged_in')\n posthog.capture('some-custom-action', distinct_id='distinct_id_of_the_user')" + }, + { + "id": "example_3", + "name": "Set event properties", + "code": "# Set event properties\nposthog.capture(\n \"user_signed_up\",\n distinct_id=\"distinct_id_of_the_user\",\n properties={\n \"login_type\": \"email\",\n \"is_free_trial\": \"true\"\n }\n)" + }, + { + "id": "example_4", + "name": "Page view event", + "code": "# Page view event\nposthog.capture('$pageview', distinct_id=\"distinct_id_of_the_user\", properties={'$current_url': 'https://example.com'})" + } + ] + }, + { + "id": "capture_ai", + "title": "capture_ai", + "description": "Capture an AI event on the dedicated AI capture endpoint. Beta: the signature is stable; operational limits (per-event size cap, batching, endpoint) may change without notice. Takes the same arguments and returns the same value as `capture()`: the event UUID, or None when the event was not admitted (disabled client, or dropped by `before_send`). The event is queued on an isolated AI lane with its own consumer pool and a higher per-event size cap, posting to the dedicated AI ingestion endpoint. The payload is sent as given \u2014 no redaction or truncation is applied here.", + "details": "", + "category": "Capture", + "params": [ + { + "name": "event", + "description": "", + "isOptional": true, + "type": "str" + }, + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalCaptureArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + } + }, + { + "id": "capture_exception", + "title": "capture_exception", + "description": "Capture an exception for error tracking. When OpenTelemetry is installed and a valid span is active, its trace and span IDs are added as ``$trace_id`` and ``$span_id`` event properties.", + "details": "", + "category": "Error Tracking", + "params": [ + { + "name": "exception", + "description": "The exception to capture.", + "isOptional": true, + "type": "BaseException" + }, + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalCaptureArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "try:\n # Some code that might fail\n pass\nexcept Exception as e:\n posthog.capture_exception(e, 'user_distinct_id', properties=additional_properties)" + } + ] + }, + { + "id": "evaluate_flags", + "title": "evaluate_flags", + "description": "Evaluate all feature flags for a user in a single call and return a :class:`FeatureFlagEvaluations` snapshot. Branch on ``.is_enabled()`` / ``.get_flag()`` and pass the same snapshot to :meth:`capture` via the ``flags`` option so events carry the exact flag values the code branched on. Prefer this over repeated ``get_feature_flag()`` calls and over ``capture(send_feature_flags=True)`` \u2014 it consolidates flag evaluation into a single ``/flags`` request per incoming request. Local evaluation is transparent: when the poller resolves a flag, the snapshot's ``$feature_flag_called`` events are tagged ``locally_evaluated=True`` and reason ``\"Evaluated locally\"``.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The user's distinct ID. If ``None``, falls back to the context distinct_id. If still unresolvable, returns an empty snapshot.", + "isOptional": false, + "type": "Number" + }, + { + "name": "groups", + "description": "Mapping of group type to group key.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "Person properties to use for evaluation.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "Group properties keyed by group type.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "If True, never fall back to remote evaluation \u2014 flags that can't be evaluated locally are omitted from the snapshot and listed with a reason in its ``unresolved_flags``.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup.", + "isOptional": true, + "type": "bool" + }, + { + "name": "flag_keys", + "description": "Optional list that scopes local evaluation, the underlying ``/flags`` request, and the returned snapshot. When omitted or ``None``, all flags are evaluated. An empty list returns an empty snapshot without evaluating flags. A requested key absent from loaded local definitions is included in one remote fallback per ``evaluate_flags`` call unless ``only_evaluate_locally`` is True. If the server also does not know the key, it is omitted from the snapshot.", + "isOptional": true, + "type": "list[str]" + }, + { + "name": "device_id", + "description": "Optional device ID override. If not provided, falls back to the context device_id (which may be set via tracing headers). Used by experience-continuity flags to match users across distinct_id changes.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "FeatureFlagEvaluations" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "flags = posthog.evaluate_flags(\n \"user_123\",\n person_properties={\"plan\": \"enterprise\"},\n)\nif flags.is_enabled(\"new-dashboard\"):\n render_new_dashboard()\nposthog.capture(\"page_viewed\", distinct_id=\"user_123\", flags=flags)" + } + ] + }, + { + "id": "feature_enabled", + "title": "feature_enabled", + "description": "Check if a feature flag is enabled for a user.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "The feature flag key.", + "isOptional": true, + "type": "str" + }, + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to only evaluate locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "send_feature_flag_events", + "description": "Whether to send feature flag events.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[bool]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "is_my_flag_enabled = posthog.feature_enabled('flag-key', 'distinct_id_of_your_user')\nif is_my_flag_enabled:\n # Do something differently for this user\n # Optional: fetch the payload\n matched_flag_payload = posthog.get_feature_flag_payload('flag-key', 'distinct_id_of_your_user')" + } + ] + }, + { + "id": "feature_flag_definitions", + "title": "feature_flag_definitions", + "description": "Return feature flag definitions loaded for local evaluation. Returns: The currently loaded feature flag definitions, or ``None`` before local evaluation has loaded definitions.", + "details": "", + "category": "Feature flags", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "flush", + "title": "flush", + "description": "Attempt immediate delivery of queued events, ended spans and autocaptured metrics, keeping the client reusable. For a client shared across requests or serverless invocations (including the module-level client), call ``flush()`` before a short-lived invocation returns. Use ``shutdown()`` instead only for final process cleanup or when disposing of a client that will not be reused. Flush does not stop the background workers or feature flag poller, and does not end open spans. Returning does not guarantee server receipt: pending work can remain after a timeout or span export failure, and failed events may be dropped by the consumers. Calls made directly from SDK callbacks such as ``on_error`` are deferred rather than blocking the worker that invoked the callback.", + "details": "", + "category": null, + "params": [ + { + "name": "timeout_seconds", + "description": "Seconds to wait for event queues to drain, shared across event lanes. Defaults to 10 seconds. Pass ``None`` to wait without a time limit; this does not guarantee successful delivery. Queued spans flush concurrently. The span exporter waits up to this many seconds for an in-flight flush, then uses a fresh drain budget once it acquires the lock. If the lock wait times out, the in-flight flush is left to finish. At least one span request is attempted after acquiring the lock, even with no budget left; retriable failures are retried after backoff while budget remains, with one last attempt at the deadline. Each span request is bounded by the client's ``timeout`` in seconds, and the last request is not cut short. This is therefore not a strict wall-clock limit for the whole call. With ``None``, a retriable span failure is left for a later flush rather than retried within this call.", + "isOptional": true, + "type": "float" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.capture('event_name', distinct_id='user_id')\n# Before a serverless invocation returns; keep the shared client open.\nposthog.flush(timeout_seconds=10)" + } + ] + }, + { + "id": "get_active_span", + "title": "get_active_span", + "description": "The span that is active in the current context, or ``None``. Alpha. Only entering a span (``with posthog.start_span(...) as span:``) makes it active; a span started manually is not. Use it to propagate the trace to the next service: ``span.traceparent()`` is the header value.", + "details": "", + "category": "Tracing", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[Span]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "span = posthog.get_active_span()\nif span is not None:\n headers[\"traceparent\"] = span.traceparent()" + } + ] + }, + { + "id": "get_all_flags", + "title": "get_all_flags", + "description": "Get all feature flags for a user.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to only evaluate locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "flag_keys_to_evaluate", + "description": "A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance.", + "isOptional": true, + "type": "list[str]" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[dict[str, Union[bool, str]]]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.get_all_flags('distinct_id_of_your_user')" + } + ] + }, + { + "id": "get_all_flags_and_payloads", + "title": "get_all_flags_and_payloads", + "description": "Get all feature flags and their payloads for a user.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to only evaluate locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "flag_keys_to_evaluate", + "description": "A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance.", + "isOptional": true, + "type": "list[str]" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "FlagsAndPayloads" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.get_all_flags_and_payloads('distinct_id_of_your_user')" + } + ] + }, + { + "id": "get_feature_flag", + "title": "get_feature_flag", + "description": "Get multivariate feature flag value for a user.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "The feature flag key.", + "isOptional": true, + "type": "str" + }, + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to only evaluate locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "send_feature_flag_events", + "description": "Whether to send feature flag events.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Union[bool, str, any]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "enabled_variant = posthog.get_feature_flag('flag-key', 'distinct_id_of_your_user')\nif enabled_variant == 'variant-key': # replace 'variant-key' with the key of your variant\n # Do something differently for this user\n # Optional: fetch the payload\n matched_flag_payload = posthog.get_feature_flag_payload('flag-key', 'distinct_id_of_your_user')" + } + ] + }, + { + "id": "get_feature_flag_evaluation_runtime", + "title": "get_feature_flag_evaluation_runtime", + "description": "Return where a locally loaded feature flag is meant to be evaluated.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "The feature flag key.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[FeatureFlagEvaluationRuntime]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import FeatureFlagEvaluationRuntime\n\nruntime = posthog.get_feature_flag_evaluation_runtime(\"my-flag\")\nif runtime is FeatureFlagEvaluationRuntime.SERVER:\n ..." + } + ] + }, + { + "id": "get_feature_flag_keys_by_evaluation_runtime", + "title": "get_feature_flag_keys_by_evaluation_runtime", + "description": "Return the keys of locally loaded flags that a runtime can evaluate. A flag set to ``FeatureFlagEvaluationRuntime.ALL`` suits either runtime, so it is returned for ``CLIENT`` and for ``SERVER``, and asking for ``ALL`` returns every loaded flag. Use this to decide which flags to hand to a browser when a backend serves flags to its own frontend.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "evaluation_runtime", + "description": "The runtime to match, as a ``FeatureFlagEvaluationRuntime`` or its string value.", + "isOptional": true, + "type": "FeatureFlagEvaluationRuntime" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "list[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import FeatureFlagEvaluationRuntime\n\nclient_keys = posthog.get_feature_flag_keys_by_evaluation_runtime(\n FeatureFlagEvaluationRuntime.CLIENT\n)" + } + ] + }, + { + "id": "get_feature_flag_payload", + "title": "get_feature_flag_payload", + "description": "Get the payload for a feature flag.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "The feature flag key.", + "isOptional": true, + "type": "str" + }, + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "match_value", + "description": "The specific flag value to get payload for.", + "isOptional": false, + "type": "bool" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to only evaluate locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "send_feature_flag_events", + "description": "Deprecated. Use get_feature_flag() instead if you need events.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[object]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "is_my_flag_enabled = posthog.feature_enabled('flag-key', 'distinct_id_of_your_user')\n\nif is_my_flag_enabled:\n # Do something differently for this user\n # Optional: fetch the payload\n matched_flag_payload = posthog.get_feature_flag_payload('flag-key', 'distinct_id_of_your_user')" + } + ] + }, + { + "id": "get_feature_flag_result", + "title": "get_feature_flag_result", + "description": "Get a FeatureFlagResult object which contains the flag result and payload for a key by evaluating locally or remotely depending on whether local evaluation is enabled and the flag can be locally evaluated. This also captures the `$feature_flag_called` event unless `send_feature_flag_events` is `False`.", + "details": "", + "category": null, + "params": [ + { + "name": "key", + "description": "The feature flag key.", + "isOptional": true, + "type": "str" + }, + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to only evaluate locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "send_feature_flag_events", + "description": "Whether to send feature flag events.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[FeatureFlagResult]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "flag_result = posthog.get_feature_flag_result('flag-key', 'distinct_id_of_your_user')\nif flag_result and flag_result.get_value() == 'variant-key':\n # Do something differently for this user\n # Optional: fetch the payload\n matched_flag_payload = flag_result.payload" + } + ] + }, + { + "id": "get_feature_flags_and_payloads", + "title": "get_feature_flags_and_payloads", + "description": "Get feature flags and payloads for a user.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "flag_keys_to_evaluate", + "description": "A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance.", + "isOptional": true, + "type": "list[str]" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "FlagsAndPayloads" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "result = posthog.get_feature_flags_and_payloads('')" + } + ] + }, + { + "id": "get_feature_payloads", + "title": "get_feature_payloads", + "description": "Get feature flag payloads for a user, preserving valid serialized JSON.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "flag_keys_to_evaluate", + "description": "A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance.", + "isOptional": true, + "type": "list[str]" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "dict[str, Optional[str]]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "payloads = posthog.get_feature_payloads('')" + } + ] + }, + { + "id": "get_feature_variants", + "title": "get_feature_variants", + "description": "Get feature flag variants for a user.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "flag_keys_to_evaluate", + "description": "A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance.", + "isOptional": true, + "type": "list[str]" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "dict[str, Union[bool, str]]" + } + }, + { + "id": "get_flags_decision", + "title": "get_flags_decision", + "description": "Get feature flags decision.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": false, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "flag_keys_to_evaluate", + "description": "A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance.", + "isOptional": true, + "type": "list[str]" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "FlagsResponse" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "decision = posthog.get_flags_decision('user123')" + } + ] + }, + { + "id": "get_remote_config_payload", + "title": "get_remote_config_payload", + "description": "Get the payload for a remote config feature flag.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "The remote config feature flag key.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "get_tags", + "title": "get_tags", + "description": "Get all tags from the current context. Returns: Dict of all tags in the current context.", + "details": "", + "category": "Contexts", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "dict[str, Any]" + } + }, + { + "id": "group_identify", + "title": "group_identify", + "description": "Identify a group and set its properties.", + "details": "", + "category": "Identification", + "params": [ + { + "name": "group_type", + "description": "The type of group (e.g., 'company', 'team'). Required - the call is dropped with a warning if it is missing or empty.", + "isOptional": true, + "type": "str" + }, + { + "name": "group_key", + "description": "The unique identifier for the group. Required - the call is dropped with a warning if it is missing or empty.", + "isOptional": true, + "type": "str" + }, + { + "name": "properties", + "description": "A dictionary of properties to set on the group.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "timestamp", + "description": "The timestamp of the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC.", + "isOptional": false, + "type": "datetime" + }, + { + "name": "uuid", + "description": "A unique identifier for the event. If provided, it must be a valid UUID string or uuid.UUID instance; invalid values are ignored and replaced with a newly generated UUID.", + "isOptional": false, + "type": "str" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this event.", + "isOptional": true, + "type": "bool" + }, + { + "name": "distinct_id", + "description": "The distinct ID of the user performing the action.", + "isOptional": false, + "type": "Number" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.group_identify('company', 'company_id_in_your_db', {\n 'name': 'Awesome Inc.',\n 'employees': 11\n})" + } + ] + }, + { + "id": "identify_context", + "title": "identify_context", + "description": "Identify the current context with a distinct ID.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "distinct_id", + "description": "The distinct ID to associate with the current context and its children.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + } + }, + { + "id": "join", + "title": "join", + "description": "Attempt to process queued events and end the consumer threads. Do not use directly, call `shutdown()` instead. Failed or undrainable events may be dropped and reported through logging or ``on_error``; returning does not guarantee server receipt. Lifecycle cleanup is attempted once, and cleanup failures are logged without retry.", + "details": "", + "category": null, + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.join()" + } + ] + }, + { + "id": "load_feature_flags", + "title": "load_feature_flags", + "description": "Load feature flags for local evaluation.", + "details": "", + "category": "Feature flags", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.load_feature_flags()" + } + ] + }, + { + "id": "new_context", + "title": "new_context", + "description": "Create a new context for managing shared state. Learn more about [contexts](/docs/libraries/python#contexts).", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "fresh", + "description": "Whether to create a fresh context that doesn't inherit from parent.", + "isOptional": false, + "type": "bool" + }, + { + "name": "capture_exceptions", + "description": "Whether to automatically capture exceptions in this context. If omitted, defaults to this client's exception autocapture setting.", + "isOptional": true, + "type": "bool" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "with client.new_context():\n client.identify_context('')\n client.capture('event_name')" + } + ] + }, + { + "id": "scoped", + "title": "scoped", + "description": "Decorator that creates a new context for the wrapped function using this client.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "fresh", + "description": "Whether to create a fresh context that doesn't inherit from parent.", + "isOptional": false, + "type": "bool" + }, + { + "name": "capture_exceptions", + "description": "Whether to automatically capture exceptions in this context. If omitted, defaults to this client's exception autocapture setting.", + "isOptional": true, + "type": "bool" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "set", + "title": "set", + "description": "Set properties on a person profile.", + "details": "", + "category": "Identification", + "params": [ + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalSetArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Set with distinct id", + "code": "# Set with distinct id\nposthog.set(distinct_id='user123', properties={'name': 'Max Hedgehog'})" + } + ] + }, + { + "id": "set_context_device_id", + "title": "set_context_device_id", + "description": "Set the device ID for the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "device_id", + "description": "The device ID to associate with the current context and its children.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + } + }, + { + "id": "set_context_session", + "title": "set_context_session", + "description": "Set the session ID for the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "session_id", + "description": "The session ID to associate with the current context and its children.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + } + }, + { + "id": "set_once", + "title": "set_once", + "description": "Set properties on a person profile only if they haven't been set before.", + "details": "", + "category": "Identification", + "params": [ + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalSetArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.set_once(distinct_id='user123', properties={'initial_signup_date': '2024-01-01'})" + } + ] + }, + { + "id": "shutdown", + "title": "shutdown", + "description": "Attempt final delivery and permanently shut down the client. Call this for final process cleanup or when disposing of a client that will not be reused. It closes capture lanes, stops background workers and the feature flag poller, and tears down integrations. Do not call it at the end of each request or serverless invocation when the client is shared; call ``flush()`` before returning instead. A shut-down client cannot be reused; create a new client if needed. Normally this method blocks until queued events have been attempted and cleanup finishes. Failed or undrainable events may be dropped and reported through logging or ``on_error``; returning does not guarantee server receipt. Event draining has no timeout. Queued spans get one final flush with up to 30 seconds waiting for an in-flight flush, then a fresh 30-second drain budget; the last HTTP request is not cut short. Any spans it cannot send are discarded with a warning, as are spans still open. Lifecycle cleanup is attempted once, and cleanup failures are logged without retry. When called directly from an SDK callback such as ``on_error``, shutdown is deferred to avoid blocking the worker that invoked the callback. If the callback must coordinate a blocking shutdown, have it signal an application-owned thread and return before that thread calls shutdown. Do not wait inside the callback for another thread or task that calls a lifecycle method.", + "details": "", + "category": null, + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.shutdown()" + } + ] + }, + { + "id": "start_span", + "title": "start_span", + "description": "Start a span for distributed tracing. Alpha. Returns a span handle. Use it as a context manager to make it the active span for the block and end it on exit (recording a raised exception on the way out); or call ``end()`` yourself for a span that cannot wrap a block. Spans started inside the block nest under it automatically. Always returns a usable handle, even when tracing is off, so calling code never branches.", + "details": "", + "category": "Tracing", + "params": [ + { + "name": "name", + "description": "A low-cardinality operation name, e.g. ``GET /users/:id``. Variable values belong in attributes, not the name.", + "isOptional": true, + "type": "str" + }, + { + "name": "kind", + "description": "``internal`` (default), ``server``, ``client``, ``producer`` or ``consumer``.", + "isOptional": true, + "type": "str" + }, + { + "name": "attributes", + "description": "Initial attributes.", + "isOptional": true, + "type": "Mapping[str, Any]" + }, + { + "name": "parent", + "description": "A span handle, or an inbound W3C ``traceparent`` header value to continue a remote trace. Defaults to the active span. A forked child starts with no active span; pass the parent span to continue a trace across a fork.", + "isOptional": false, + "type": "Span" + }, + { + "name": "tracestate", + "description": "The inbound ``tracestate`` header accompanying a ``traceparent`` string ``parent``; preserved and propagated.", + "isOptional": true, + "type": "str" + }, + { + "name": "start_time", + "description": "A ``datetime`` or epoch seconds, to backdate the span.", + "isOptional": false, + "type": "datetime" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Span" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog = Posthog(\"\", traces={\"service_name\": \"checkout-api\"})\n\nwith posthog.start_span(\"POST /checkout\", parent=request.headers.get(\"traceparent\")) as span:\n span.set_attribute(\"plan\", user.plan)\n with posthog.start_span(\"db.query\", kind=\"client\"):\n ...\n outgoing_headers = {\"traceparent\": span.traceparent()}" + } + ] + }, + { + "id": "tag", + "title": "tag", + "description": "Add a tag to the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "name", + "description": "The tag key.", + "isOptional": true, + "type": "str" + }, + { + "name": "value", + "description": "The tag value.", + "isOptional": true, + "type": "Any" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + } + } + ] + }, + { + "id": "PostHogModule", + "title": "PostHog Module Functions", + "description": "Global functions available in the PostHog module", + "functions": [ + { + "id": "alias", + "title": "alias", + "description": "Associate user behaviour before and after they e.g. register, login, or perform some other identifying action.", + "details": "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?\". Particularly useful for associating user behaviour before and after they e.g. register, login, or perform some other identifying action.", + "category": "Identification", + "params": [ + { + "name": "previous_id", + "description": "The unique ID of the user before", + "isOptional": true, + "type": "Number" + }, + { + "name": "distinct_id", + "description": "The current unique id", + "isOptional": true, + "type": "str" + }, + { + "name": "timestamp", + "description": "Optional timestamp for the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC.", + "isOptional": false, + "type": "datetime" + }, + { + "name": "uuid", + "description": "Optional UUID for the event", + "isOptional": true, + "type": "str" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup", + "isOptional": true, + "type": "bool" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Alias user", + "code": "# Alias user\nfrom posthog import alias\nalias(previous_id='distinct_id', distinct_id='alias_id')" + } + ] + }, + { + "id": "capture", + "title": "capture", + "description": "Capture anything a user does within your system.", + "details": "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. A capture call requires an event name to specify the event. We recommend using [verb] [noun], like `movie played` or `movie updated` to easily identify what your events mean later on. Capture takes a number of optional arguments, which are defined by the `OptionalCaptureArgs` type.", + "category": "Events", + "params": [ + { + "name": "event", + "description": "The event name to specify the event **kwargs: Optional arguments including:", + "isOptional": true, + "type": "str" + }, + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalCaptureArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Context and capture usage", + "code": "# Context and capture usage\nfrom posthog import new_context, identify_context, tag_context, capture\n# Enter a new context (e.g. a request/response cycle, an instance of a background job, etc)\nwith new_context():\n # Associate this context with some user, by distinct_id\n identify_context('some user')\n\n # Capture an event, associated with the context-level distinct ID ('some user')\n capture('movie started')\n\n # Capture an event associated with some other user (overriding the context-level distinct ID)\n capture('movie joined', distinct_id='some-other-user')\n\n # Capture an event with some properties\n capture('movie played', properties={'movie_id': '123', 'category': 'romcom'})\n\n # Capture an event with some properties\n capture('purchase', properties={'product_id': '123', 'category': 'romcom'})\n # Capture an event with some associated group\n capture('purchase', groups={'company': 'id:5'})\n\n # Adding a tag to the current context will cause it to appear on all subsequent events\n tag_context('some-tag', 'some-value')\n\n capture('another-event') # Will be captured with `'some-tag': 'some-value'` in the properties dict" + }, + { + "id": "example_2", + "name": "Set event properties", + "code": "# Set event properties\nfrom posthog import capture\ncapture(\n \"user_signed_up\",\n distinct_id=\"distinct_id_of_the_user\",\n properties={\n \"login_type\": \"email\",\n \"is_free_trial\": \"true\"\n }\n)" + } + ] + }, + { + "id": "capture_ai", + "title": "capture_ai", + "description": "Capture an AI event on the dedicated AI capture endpoint. Beta: the signature is stable; operational limits (per-event size cap, batching, endpoint) may change without notice. Takes the same arguments and returns the same value as `capture()`: the event UUID, or None when the event was not admitted (disabled client, or dropped by `before_send`). The event is delivered on an isolated queue with its own consumer pool and a higher per-event size cap, posting to the dedicated AI ingestion endpoint. The payload is sent as given \u2014 no redaction or truncation is applied here.", + "details": "", + "category": "Events", + "params": [ + { + "name": "event", + "description": "The event name, normally one of the `$ai_*` event names. **kwargs: Same optional arguments as `capture()`.", + "isOptional": true, + "type": "str" + }, + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalCaptureArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import capture_ai\n\nuuid = capture_ai(\n \"$ai_generation\",\n distinct_id=\"user_123\",\n properties={\"$ai_model\": \"gpt-5\"},\n)" + } + ] + }, + { + "id": "capture_exception", + "title": "capture_exception", + "description": "Capture exceptions that happen in your code.", + "details": "Capture exception is idempotent - if it is called twice with the same exception instance, only a occurrence will be tracked in posthog. This is because, generally, contexts will cause exceptions to be captured automatically. However, to ensure you track an exception, if you catch and do not re-raise it, capturing it manually is recommended, unless you are certain it will have crossed a context boundary (e.g. by existing a `with posthog.new_context():` block already). If the passed exception was raised and caught, the captured stack trace will consist of every frame between where the exception was raised and the point at which it is captured (the \"traceback\"). If the passed exception was never raised, e.g. if you call `posthog.capture_exception(ValueError(\"Some Error\"))`, the stack trace captured will be the full stack trace at the moment the exception was captured. Note that heavy use of contexts will lead to truncated stack traces, as the exception will be captured by the context entered most recently, 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. `capture_exception` takes the same set of optional arguments as `capture`.", + "category": "Events", + "params": [ + { + "name": "exception", + "description": "The exception to capture. If not provided, the current exception is captured via `sys.exc_info()` **kwargs: Optional capture arguments including distinct_id, properties, timestamp, uuid, groups, flags, send_feature_flags, and disable_geoip. Overriding reserved exception properties through ``properties`` is deprecated and will stop working in the next major version.", + "isOptional": false, + "type": "BaseException" + }, + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalCaptureArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Capture exception", + "code": "# Capture exception\nfrom posthog import capture_exception\ntry:\n risky_operation()\nexcept Exception as e:\n capture_exception(e)" + } + ] + }, + { + "id": "evaluate_flags", + "title": "evaluate_flags", + "description": "Evaluate all feature flags for a user in a single call and return a :class:`FeatureFlagEvaluations` snapshot. Branch on ``.is_enabled()`` / ``.get_flag()`` and pass the same snapshot to ``capture()`` via the ``flags`` option so events carry the exact flag values the code branched on. Prefer this over repeated ``get_feature_flag()`` calls and over ``capture(send_feature_flags=True)`` \u2014 it consolidates flag evaluation into a single ``/flags`` request per incoming request.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The user's distinct ID. If ``None``, falls back to the context distinct_id. If still unresolvable, returns an empty snapshot.", + "isOptional": false, + "type": "Number" + }, + { + "name": "groups", + "description": "Mapping of group type to group key.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "Person properties to use for evaluation.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "Group properties keyed by group type.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "If ``True``, never fall back to remote evaluation and omit flags that cannot be evaluated locally. The snapshot's ``unresolved_flags`` lists them with a reason.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup.", + "isOptional": true, + "type": "bool" + }, + { + "name": "flag_keys", + "description": "Optional list that scopes local evaluation, the underlying ``/flags`` request, and the returned snapshot. When omitted or ``None``, all flags are evaluated. An empty list returns an empty snapshot without evaluating flags. A requested key absent from loaded local definitions is included in one remote fallback per ``evaluate_flags`` call unless ``only_evaluate_locally`` is ``True``. If the server also does not know the key, it is omitted from the snapshot.", + "isOptional": true, + "type": "list[str]" + }, + { + "name": "device_id", + "description": "Optional device ID override. If not provided, falls back to the context device_id (which may be set via tracing headers). Used by experience-continuity flags to match users across distinct_id changes.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "FeatureFlagEvaluations" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import evaluate_flags, capture\nflags = evaluate_flags(\"user_123\", person_properties={\"plan\": \"enterprise\"})\nif flags.is_enabled(\"new-dashboard\"):\n render_new_dashboard()\ncapture(\"page_viewed\", distinct_id=\"user_123\", flags=flags)" + } + ] + }, + { + "id": "feature_enabled", + "title": "feature_enabled", + "description": "Use feature flags to enable or disable features for users.", + "details": "You can call `posthog.load_feature_flags()` before to make sure you're not doing unexpected requests.", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "The feature flag key", + "isOptional": true, + "type": "str" + }, + { + "name": "distinct_id", + "description": "The user's distinct ID", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "Groups mapping", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "Person properties", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "Group properties", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to evaluate only locally", + "isOptional": false, + "type": "bool" + }, + { + "name": "send_feature_flag_events", + "description": "Whether to send feature flag events", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "Optional device ID override for experience-continuity flags", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[bool]" + }, + "examples": [ + { + "id": "example_1", + "name": "Boolean feature flag", + "code": "# Boolean feature flag\nfrom posthog import feature_enabled, get_feature_flag_payload\nis_my_flag_enabled = feature_enabled('flag-key', 'distinct_id_of_your_user')\nif is_my_flag_enabled:\n matched_flag_payload = get_feature_flag_payload('flag-key', 'distinct_id_of_your_user')" + } + ] + }, + { + "id": "feature_flag_definitions", + "title": "feature_flag_definitions", + "description": "Returns loaded feature flags.", + "details": "Returns loaded feature flags, if any. Helpful for debugging what flag information you have loaded.", + "category": "Feature flags", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import feature_flag_definitions\ndefinitions = feature_flag_definitions()" + } + ] + }, + { + "id": "flush", + "title": "flush", + "description": "Attempt immediate delivery of queued events and ended spans, keeping the global client reusable. Call ``flush()`` before a short-lived request or serverless invocation returns when using the shared module-level client. Use ``shutdown()`` only for final process cleanup or a client that will not be reused. Flush does not stop the background workers or feature flag poller, and does not end open spans. Returning does not guarantee server receipt: pending work can remain after a timeout or span export failure, and failed events may be dropped by consumers. Calls made directly from SDK callbacks such as ``on_error`` are deferred rather than blocking the worker that invoked the callback.", + "details": "", + "category": "Client management", + "params": [ + { + "name": "timeout_seconds", + "description": "Seconds to wait for event queues to drain, shared across event lanes. Defaults to 10 seconds. Pass ``None`` to wait without a time limit; this does not guarantee successful delivery. Queued spans flush concurrently, waiting up to this many seconds for an in-flight flush before starting a fresh drain budget. A lock wait timeout leaves the in-flight flush to finish. At least one span request is attempted after acquiring the lock, even with no budget left; retriable failures are retried after backoff while budget remains, with one last attempt at the deadline. The last span request is not cut short and is bounded by the client's ``timeout`` in seconds, so this is not a strict wall-clock limit for the whole call. With ``None``, a retriable span failure is left for a later flush rather than retried within this call.", + "isOptional": true, + "type": "float" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "import posthog\nposthog.capture('event_name', distinct_id='user_id')\n# Before a serverless invocation returns; keep the global client open.\nposthog.flush(timeout_seconds=10)" + } + ] + }, + { + "id": "get_active_span", + "title": "get_active_span", + "description": "The span that is active in the current context, or ``None``. Alpha. Only entering a span (``with posthog.start_span(...) as span:``) makes it active; a span started manually is not. Use it to propagate the trace to the next service: ``span.traceparent()`` is the header value.", + "details": "", + "category": "Tracing", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[Span]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "span = posthog.get_active_span()\nif span is not None:\n headers[\"traceparent\"] = span.traceparent()" + } + ] + }, + { + "id": "get_all_flags", + "title": "get_all_flags", + "description": "Get all flags for a given user.", + "details": "Flags are key-value pairs where the key is the flag key and the value is the flag variant, or True, or False.", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The user's distinct ID", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "Groups mapping", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "Person properties", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "Group properties", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to evaluate only locally", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "Optional device ID override for experience-continuity flags", + "isOptional": true, + "type": "str" + }, + { + "name": "flag_keys_to_evaluate", + "description": "Optional list of flag keys to evaluate (evaluates all if None)", + "isOptional": true, + "type": "list[str]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[dict[str, Union[bool, str]]]" + }, + "examples": [ + { + "id": "example_1", + "name": "All flags for user", + "code": "# All flags for user\nfrom posthog import get_all_flags\nget_all_flags('distinct_id_of_your_user')" + } + ] + }, + { + "id": "get_all_flags_and_payloads", + "title": "get_all_flags_and_payloads", + "description": "Get all feature flag values and payloads for a user.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The user's distinct ID.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "Mapping of group type to group key.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "Person properties to use for evaluation.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "Group properties keyed by group type.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to evaluate only locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup.", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "Optional device ID override for experience-continuity flags.", + "isOptional": true, + "type": "str" + }, + { + "name": "flag_keys_to_evaluate", + "description": "Optional list of flag keys to evaluate. Evaluates all flags when omitted.", + "isOptional": true, + "type": "list[str]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "FlagsAndPayloads" + } + }, + { + "id": "get_feature_flag", + "title": "get_feature_flag", + "description": "Get feature flag variant for users. Used with experiments.", + "details": "`groups` are a mapping from group type to group key. So, if you have a group type of \"organization\" and a group key of \"5\", you would pass groups={\"organization\": \"5\"}. `group_properties` take the format: { group_type_name: { group_properties } }. So, for example, if you have the group type \"organization\" and the group key \"5\", with the properties name, and employee count, you'll send these as: group_properties={\"organization\": {\"name\": \"PostHog\", \"employees\": 11}}.", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "The feature flag key", + "isOptional": true, + "type": "str" + }, + { + "name": "distinct_id", + "description": "The user's distinct ID", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "Groups mapping from group type to group key", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "Person properties", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "Group properties in format { group_type_name: { group_properties } }", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to evaluate only locally", + "isOptional": false, + "type": "bool" + }, + { + "name": "send_feature_flag_events", + "description": "Whether to send feature flag events", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "Optional device ID override for experience-continuity flags", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Union[bool, str, any]" + }, + "examples": [ + { + "id": "example_1", + "name": "Multivariate feature flag", + "code": "# Multivariate feature flag\nfrom posthog import get_feature_flag, get_feature_flag_payload\nenabled_variant = get_feature_flag('flag-key', 'distinct_id_of_your_user')\nif enabled_variant == 'variant-key':\n matched_flag_payload = get_feature_flag_payload('flag-key', 'distinct_id_of_your_user')" + } + ] + }, + { + "id": "get_feature_flag_evaluation_runtime", + "title": "get_feature_flag_evaluation_runtime", + "description": "Return where a locally loaded feature flag is meant to be evaluated.", + "details": "Reads the `evaluation_runtime` each flag definition carries, so no extra request is made. Returns `None` when local evaluation has not loaded a definition for this key. A definition that carries no runtime reports `FeatureFlagEvaluationRuntime.ALL`, the default PostHog applies.", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[FeatureFlagEvaluationRuntime]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import FeatureFlagEvaluationRuntime, get_feature_flag_evaluation_runtime\nruntime = get_feature_flag_evaluation_runtime(\"my-flag\")" + } + ] + }, + { + "id": "get_feature_flag_keys_by_evaluation_runtime", + "title": "get_feature_flag_keys_by_evaluation_runtime", + "description": "Return the keys of locally loaded flags that a runtime can evaluate.", + "details": "A flag set to `FeatureFlagEvaluationRuntime.ALL` suits either runtime, so it is returned for `CLIENT` and for `SERVER`. Use this to decide which flags to hand to a browser when a backend serves flags to its own frontend.", + "category": "Feature flags", + "params": [ + { + "name": "evaluation_runtime", + "description": "", + "isOptional": true, + "type": "FeatureFlagEvaluationRuntime" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "list[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import FeatureFlagEvaluationRuntime, get_feature_flag_keys_by_evaluation_runtime\nclient_keys = get_feature_flag_keys_by_evaluation_runtime(FeatureFlagEvaluationRuntime.CLIENT)" + } + ] + }, + { + "id": "get_feature_flag_payload", + "title": "get_feature_flag_payload", + "description": "Get the payload associated with a feature flag value. Deprecated for new code. Prefer ``evaluate_flags()`` and ``flags.get_flag_payload(key)`` so flag evaluation happens once per request.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "The feature flag key.", + "isOptional": true, + "type": "str" + }, + { + "name": "distinct_id", + "description": "The user's distinct ID.", + "isOptional": true, + "type": "Number" + }, + { + "name": "match_value", + "description": "Optional flag value to use when selecting a payload.", + "isOptional": false, + "type": "bool" + }, + { + "name": "groups", + "description": "Mapping of group type to group key.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "Person properties to use for evaluation.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "Group properties keyed by group type.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to evaluate only locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "send_feature_flag_events", + "description": "Whether to send a $feature_flag_called event.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup.", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "Optional device ID override for experience-continuity flags.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[object]" + } + }, + { + "id": "get_feature_flag_result", + "title": "get_feature_flag_result", + "description": "Get a FeatureFlagResult object which contains the flag result and payload. This method evaluates a feature flag and returns a FeatureFlagResult object containing: - enabled: Whether the flag is enabled - variant: The variant value if the flag has variants - payload: The payload associated with the flag (automatically deserialized from JSON) - key: The flag key - reason: Why the flag was enabled/disabled", + "details": "", + "category": null, + "params": [ + { + "name": "key", + "description": "The feature flag key.", + "isOptional": true, + "type": "str" + }, + { + "name": "distinct_id", + "description": "The user's distinct ID.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "Mapping of group type to group key.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "Person properties to use for evaluation.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "Group properties keyed by group type.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to evaluate only locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "send_feature_flag_events", + "description": "Whether to send a $feature_flag_called event.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup.", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "Optional device ID override for experience-continuity flags.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[FeatureFlagResult]" + } + }, + { + "id": "get_remote_config_payload", + "title": "get_remote_config_payload", + "description": "Get the payload for a remote config feature flag.", + "details": "", + "category": null, + "params": [ + { + "name": "key", + "description": "The key of the feature flag", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "get_tags", + "title": "get_tags", + "description": "Get all tags from the current context. Returns: Dict of all tags in the current context", + "details": "", + "category": "Contexts", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "dict[str, Any]" + } + }, + { + "id": "group_identify", + "title": "group_identify", + "description": "Set properties on a group.", + "details": "", + "category": "Identification", + "params": [ + { + "name": "group_type", + "description": "Type of your group. Required - the call is dropped with a warning if it is missing or empty.", + "isOptional": true, + "type": "str" + }, + { + "name": "group_key", + "description": "Unique identifier of the group. Required - the call is dropped with a warning if it is missing or empty.", + "isOptional": true, + "type": "str" + }, + { + "name": "properties", + "description": "Properties to set on the group", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "timestamp", + "description": "Optional timestamp for the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC.", + "isOptional": false, + "type": "datetime" + }, + { + "name": "uuid", + "description": "Optional UUID for the event", + "isOptional": true, + "type": "str" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup", + "isOptional": true, + "type": "bool" + }, + { + "name": "distinct_id", + "description": "Optional distinct ID of the user performing the action", + "isOptional": false, + "type": "Number" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Group identify", + "code": "# Group identify\nfrom posthog import group_identify\ngroup_identify('company', 'company_id_in_your_db', {\n 'name': 'Awesome Inc.',\n 'employees': 11\n})" + } + ] + }, + { + "id": "identify_context", + "title": "identify_context", + "description": "Identify the current context with a distinct ID.", + "details": "", + "category": "Identification", + "params": [ + { + "name": "distinct_id", + "description": "The distinct ID to associate with the current context and its children", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import identify_context\nidentify_context(\"user_123\")" + } + ] + }, + { + "id": "join", + "title": "join", + "description": "Attempt to process queued events and stop the client's background workers. Use `shutdown()` directly in most cases. Failed or undrainable events may be dropped and reported through logging or ``on_error``; returning does not guarantee server receipt. Lifecycle cleanup is attempted once, and cleanup failures are logged without retry.", + "details": "", + "category": "Client management", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import join\njoin()" + } + ] + }, + { + "id": "load_feature_flags", + "title": "load_feature_flags", + "description": "Load feature flag definitions from PostHog.", + "details": "", + "category": "Feature flags", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import load_feature_flags\nload_feature_flags()" + } + ] + }, + { + "id": "new_context", + "title": "new_context", + "description": "Create a new context scope that will be active for the duration of the with block.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "fresh", + "description": "Whether to start with a fresh context (default: False)", + "isOptional": false, + "type": "bool" + }, + { + "name": "capture_exceptions", + "description": "Whether to capture exceptions raised within the context. If omitted, defaults to the relevant client's exception autocapture setting.", + "isOptional": true, + "type": "bool" + }, + { + "name": "client", + "description": "Optional Posthog client instance to use for this context (default: None)", + "isOptional": true, + "type": "Client" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import new_context, tag, capture\nwith new_context():\n tag(\"request_id\", \"123\")\n capture(\"event_name\", properties={\"property\": \"value\"})" + } + ] + }, + { + "id": "scoped", + "title": "scoped", + "description": "Decorator that creates a new context for the function.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "fresh", + "description": "Whether to start with a fresh context (default: False)", + "isOptional": false, + "type": "bool" + }, + { + "name": "capture_exceptions", + "description": "Whether to capture and track exceptions with posthog error tracking. If omitted, defaults to the global exception autocapture setting.", + "isOptional": true, + "type": "bool" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import scoped, tag, capture\n@scoped()\ndef process_payment(payment_id):\n tag(\"payment_id\", payment_id)\n capture(\"payment_started\")" + } + ] + }, + { + "id": "set", + "title": "set", + "description": "Set properties on a user record.", + "details": "This will overwrite previous people property values. Generally operates similar to `capture`, with distinct_id being an optional argument, defaulting to the current context's distinct ID. If there is no context-level distinct ID, and no override distinct_id is passed, this function will do nothing. Context tags are folded into $set properties, so tagging the current context and then calling `set` will cause those tags to be set on the user (unlike capture, which causes them to just be set on the event).", + "category": "Identification", + "params": [ + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalSetArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Set person properties", + "code": "# Set person properties\nfrom posthog import set\nset(distinct_id='distinct_id', properties={'name': 'Max Hedgehog'})" + } + ] + }, + { + "id": "set_capture_exception_code_variables_context", + "title": "set_capture_exception_code_variables_context", + "description": "Override code-variable capture for exceptions in the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "enabled", + "description": "Whether exceptions captured in this context should include local variable values from stack frames.", + "isOptional": true, + "type": "bool" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "set_code_variables_detect_secrets_context", + "title": "set_code_variables_detect_secrets_context", + "description": "Whether to apply entropy-based secret detection as a last-resort redaction of high-entropy values (API keys, tokens, strong passwords) in captured code variables for the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "enabled", + "description": "", + "isOptional": true, + "type": "bool" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "set_code_variables_ignore_patterns_context", + "title": "set_code_variables_ignore_patterns_context", + "description": "Override code-variable ignore patterns for exceptions in the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "ignore_patterns", + "description": "Variable-name patterns that should be omitted entirely when code variables are captured.", + "isOptional": true, + "type": "list[str]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "set_code_variables_mask_patterns_context", + "title": "set_code_variables_mask_patterns_context", + "description": "Override code-variable mask patterns for exceptions in the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "mask_patterns", + "description": "Variable-name patterns whose values should be replaced with ``***`` when code variables are captured.", + "isOptional": true, + "type": "list[str]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "set_code_variables_mask_url_credentials_context", + "title": "set_code_variables_mask_url_credentials_context", + "description": "Whether to scrub credentials embedded in URLs/DSNs (e.g. user:pass@host) from captured code variables for the current context.", + "details": "", + "category": null, + "params": [ + { + "name": "enabled", + "description": "", + "isOptional": true, + "type": "bool" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "set_context_device_id", + "title": "set_context_device_id", + "description": "Set the device ID for the current context, associating all feature flag requests in this or child contexts with the given device ID.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "device_id", + "description": "The device ID to associate with the current context and its children", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import set_context_device_id\nset_context_device_id(\"device_123\")" + } + ] + }, + { + "id": "set_context_session", + "title": "set_context_session", + "description": "Set the session ID for the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "session_id", + "description": "The session ID to associate with the current context and its children", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import set_context_session\nset_context_session(\"session_123\")" + } + ] + }, + { + "id": "set_once", + "title": "set_once", + "description": "Set properties on a user record, only if they do not yet exist.", + "details": "This will not overwrite previous people property values, unlike `set`. Otherwise, operates in an identical manner to `set`.", + "category": "Identification", + "params": [ + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalSetArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Set property once", + "code": "# Set property once\nfrom posthog import set_once\nset_once(distinct_id='distinct_id', properties={'initial_url': '/blog'})" + } + ] + }, + { + "id": "setup", + "title": "setup", + "description": "Create or return the global PostHog client configured by module settings. Most applications should either instantiate ``Posthog`` directly or set ``posthog.api_key``/other module settings before calling top-level helpers. ``setup()`` is called automatically by global APIs such as ``capture()``. Returns: The global ``Client`` instance. If both ``api_key`` and ``project_api_key`` are missing or blank, the client is disabled and module-level calls become no-ops.", + "details": "", + "category": "Initialization", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Client" + } + }, + { + "id": "shutdown", + "title": "shutdown", + "description": "Attempt final delivery and permanently shut down the global client. Call this for final process cleanup, not at the end of each request or serverless invocation that shares the module-level client. Use ``flush()`` before returning from those invocations instead. Shutdown closes capture lanes, stops background workers and the feature flag poller, and tears down integrations. The global client is not recreated automatically after shutdown. This normally blocks until queued events have been attempted and cleanup finishes. Failed or undrainable events may be dropped and reported through logging or ``on_error``; returning does not guarantee server receipt. Event draining has no timeout. Queued spans get a final flush with up to 30 seconds waiting for an in-flight flush, then a fresh 30-second drain budget; the last HTTP request is not cut short. Unsent queued spans and still-open spans are discarded with a warning. Lifecycle cleanup is attempted once, and cleanup failures are logged without retry. Calls made directly from SDK callbacks such as ``on_error`` are deferred to avoid deadlocking the worker. If blocking completion is required, signal an application-owned thread, return from the callback, and call ``shutdown()`` from that thread. Do not wait inside a callback for another thread or task calling a lifecycle method.", + "details": "", + "category": "Client management", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import shutdown\nshutdown()" + } + ] + }, + { + "id": "start_span", + "title": "start_span", + "description": "Start a span for distributed tracing. Alpha. Returns a span handle. Use it as a context manager to make it the active span for the block and end it on exit (recording a raised exception on the way out); or call ``end()`` yourself for a span that cannot wrap a block. Spans started inside the block nest under it automatically. Always returns a usable handle, even when tracing is off, so calling code never branches.", + "details": "", + "category": "Tracing", + "params": [ + { + "name": "name", + "description": "A low-cardinality operation name, e.g. ``GET /users/:id``. Variable values belong in attributes, not the name.", + "isOptional": true, + "type": "str" + }, + { + "name": "kind", + "description": "``internal`` (default), ``server``, ``client``, ``producer`` or ``consumer``.", + "isOptional": true, + "type": "str" + }, + { + "name": "attributes", + "description": "Initial attributes.", + "isOptional": true, + "type": "Mapping[str, Any]" + }, + { + "name": "parent", + "description": "A span handle, or an inbound W3C ``traceparent`` header value to continue a remote trace. Defaults to the active span.", + "isOptional": false, + "type": "Span" + }, + { + "name": "tracestate", + "description": "The inbound ``tracestate`` header accompanying a ``traceparent`` string ``parent``; preserved and propagated.", + "isOptional": true, + "type": "str" + }, + { + "name": "start_time", + "description": "A ``datetime`` or epoch seconds, to backdate the span.", + "isOptional": false, + "type": "datetime" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Span" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "import posthog\nposthog.traces = {\"service_name\": \"checkout-api\"}\n\nwith posthog.start_span(\"POST /checkout\", parent=request.headers.get(\"traceparent\")) as span:\n span.set_attribute(\"plan\", user.plan)\n with posthog.start_span(\"db.query\", kind=\"client\"):\n ...\n outgoing_headers = {\"traceparent\": span.traceparent()}" + } + ] + }, + { + "id": "tag", + "title": "tag", + "description": "Add a tag to the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "name", + "description": "The tag key", + "isOptional": true, + "type": "str" + }, + { + "name": "value", + "description": "The tag value", + "isOptional": true, + "type": "Any" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import tag\ntag(\"user_id\", \"123\")" + } + ] + } + ] + } + ], + "categories": [ + "Initialization", + "Identification", + "Capture", + "Error Tracking", + "Feature flags", + "Tracing", + "Contexts", + "Events", + "Client management" + ] +} \ No newline at end of file diff --git a/references/posthog-python-references-latest.json b/references/posthog-python-references-latest.json index 841a5b2d..d8cd4506 100644 --- a/references/posthog-python-references-latest.json +++ b/references/posthog-python-references-latest.json @@ -2,7 +2,7 @@ "id": "posthog-python", "hogRef": "0.3", "info": { - "version": "7.80.1", + "version": "7.80.2", "id": "posthog-python", "title": "PostHog Python SDK", "description": "Integrate PostHog into any python application.", From 3a2db15cd31617ebb681724f4ddc25bbcaedaea9 Mon Sep 17 00:00:00 2001 From: mosafariuk Date: Thu, 29 Oct 2026 14:00:47 +0000 Subject: [PATCH 47/60] fix(ai): report OpenAI Agents cached tokens (#1102) * fix(ai): report OpenAI Agents cached and reasoning tokens The Agents SDK nests cached input and reasoning tokens under the usage details fields: input_tokens_details/output_tokens_details on generation span usage and on the Responses usage object of response spans. The processor read flat keys that the SDK never sets, so both were always dropped. Read the nested fields, keeping the flat keys for custom models. * fix(ai): declare inclusive cache reporting for nested Agents usage OpenAI-shaped usage counts cached tokens inside input_tokens. Declare $ai_cache_reporting_exclusive: False when the cache count comes from the nested usage details, as the Node Agents processor does, so the cost backend does not infer exclusive reporting from a Claude model name served through an OpenAI-compatible endpoint. Flat custom-model keys keep their own convention, and a caller-supplied value still takes precedence. * fix(ai): don't forward inclusive Agents reasoning tokens OpenAI-compatible providers such as OpenRouter count reasoning inside completion_tokens, while the cost backend adds reasoning on top for Gemini model names, so forwarding the nested count priced reasoning twice. Keep the original flat reasoning_tokens key only; cached tokens are unchanged. (cherry picked from commit 4d6e44766175f80fbbf538ab4369b1f3916f05f7) --- .../openai-agents-cache-reasoning-tokens.md | 5 + posthog/ai/openai_agents/processor.py | 52 +++++- .../test/ai/openai_agents/test_processor.py | 157 ++++++++++++++++++ 3 files changed, 211 insertions(+), 3 deletions(-) create mode 100644 .sampo/changesets/openai-agents-cache-reasoning-tokens.md diff --git a/.sampo/changesets/openai-agents-cache-reasoning-tokens.md b/.sampo/changesets/openai-agents-cache-reasoning-tokens.md new file mode 100644 index 00000000..342cc325 --- /dev/null +++ b/.sampo/changesets/openai-agents-cache-reasoning-tokens.md @@ -0,0 +1,5 @@ +--- +pypi/posthog: patch +--- + +Report cached input tokens from OpenAI Agents generation and response spans, which nest them under the usage details fields, and declare them inclusive. diff --git a/posthog/ai/openai_agents/processor.py b/posthog/ai/openai_agents/processor.py index db7ab15d..5a37e555 100644 --- a/posthog/ai/openai_agents/processor.py +++ b/posthog/ai/openai_agents/processor.py @@ -47,6 +47,27 @@ def _parse_iso_timestamp(iso_str: Optional[str]) -> Optional[float]: return None +def _usage_detail(usage: Any, details_keys: tuple, key: str) -> Optional[int]: + """Read a token count nested under a usage details field. + + Agents span usage is a dict (generation spans) or an OpenAI usage object + (response spans), with details under ``input_tokens_details``/ + ``output_tokens_details`` (Responses) or ``prompt_tokens_details``/ + ``completion_tokens_details`` (Chat Completions). + """ + + def get(container: Any, name: str) -> Any: + if isinstance(container, dict): + return container.get(name) + return getattr(container, name, None) + + for details_key in details_keys: + value = get(get(usage, details_key), key) + if isinstance(value, int): + return value + return None + + class PostHogTracingProcessor(TracingProcessor): """ A tracing processor that sends OpenAI Agents SDK traces to PostHog. @@ -543,11 +564,27 @@ def _handle_generation_span( ), } - # Add optional token fields if present + # Add optional token fields if present. The Agents SDK nests cached + # tokens under the usage details fields; flat keys are kept for custom + # models. Nested reasoning is not forwarded: compatible providers count + # it inside the output total, and the cost backend would add it again. if usage.get("reasoning_tokens"): properties["$ai_reasoning_tokens"] = usage["reasoning_tokens"] - if usage.get("cache_read_input_tokens"): - properties["$ai_cache_read_input_tokens"] = usage["cache_read_input_tokens"] + cache_read_tokens = usage.get("cache_read_input_tokens") + if not cache_read_tokens: + cache_read_tokens = _usage_detail( + usage, + ("input_tokens_details", "prompt_tokens_details"), + "cached_tokens", + ) + if cache_read_tokens: + # OpenAI-shaped usage counts cached tokens inside input_tokens. + # Say so explicitly: the cost backend otherwise infers exclusive + # reporting from Claude model names served through compatible + # endpoints. A caller-supplied value still takes precedence. + properties["$ai_cache_reporting_exclusive"] = False + if cache_read_tokens: + properties["$ai_cache_read_input_tokens"] = cache_read_tokens if usage.get("cache_creation_input_tokens"): properties["$ai_cache_creation_input_tokens"] = usage[ "cache_creation_input_tokens" @@ -723,6 +760,15 @@ def _handle_response_span( if total_cost_usd is not None: properties["$ai_total_cost_usd"] = total_cost_usd + if usage: + cache_read_tokens = _usage_detail( + usage, ("input_tokens_details",), "cached_tokens" + ) + if cache_read_tokens: + properties["$ai_cache_read_input_tokens"] = cache_read_tokens + # Responses input_tokens include cached tokens. + properties["$ai_cache_reporting_exclusive"] = False + # Extract output content from response if response: output_items = getattr(response, "output", None) diff --git a/posthog/test/ai/openai_agents/test_processor.py b/posthog/test/ai/openai_agents/test_processor.py index 74f2336a..422831ab 100644 --- a/posthog/test/ai/openai_agents/test_processor.py +++ b/posthog/test/ai/openai_agents/test_processor.py @@ -288,6 +288,163 @@ def test_generation_span_with_reasoning_tokens( call_kwargs = mock_client.capture_ai.call_args[1] assert call_kwargs["properties"]["$ai_reasoning_tokens"] == 400 + def test_generation_span_reads_nested_cache_tokens( + self, processor, mock_client, mock_span + ): + """The Agents SDK nests cached and reasoning tokens under the usage + details fields of generation span usage.""" + from agents.usage import Usage, model_usage_to_span_usage + from openai.types.responses.response_usage import ( + InputTokensDetails, + OutputTokensDetails, + ) + + usage = Usage( + requests=1, + input_tokens=100, + output_tokens=50, + total_tokens=150, + input_tokens_details=InputTokensDetails( + cached_tokens=60, cache_write_tokens=0 + ), + output_tokens_details=OutputTokensDetails(reasoning_tokens=40), + ) + mock_span.span_data = GenerationSpanData( + model="gpt-5-mini", usage=model_usage_to_span_usage(usage) + ) + + processor.on_span_start(mock_span) + processor.on_span_end(mock_span) + + props = mock_client.capture_ai.call_args[1]["properties"] + assert props["$ai_input_tokens"] == 100 + assert props["$ai_cache_read_input_tokens"] == 60 + assert props["$ai_output_tokens"] == 50 + # Inclusive in output_tokens; forwarding it would be counted twice. + assert "$ai_reasoning_tokens" not in props + assert props["$ai_cache_reporting_exclusive"] is False + + def test_response_span_reads_cache_tokens(self, processor, mock_client, mock_span): + """Response spans carry the OpenAI Responses usage object, with cached + and reasoning tokens in its details fields.""" + from openai.types.responses import Response + + response = Response.model_validate( + { + "id": "resp_123", + "object": "response", + "created_at": 0, + "model": "gpt-5-mini", + "status": "completed", + "parallel_tool_calls": True, + "tool_choice": "auto", + "tools": [], + "output": [], + "usage": { + "input_tokens": 100, + "output_tokens": 50, + "total_tokens": 150, + "input_tokens_details": { + "cached_tokens": 60, + "cache_write_tokens": 0, + }, + "output_tokens_details": {"reasoning_tokens": 40}, + }, + } + ) + mock_span.span_data = ResponseSpanData(response=response, input="Hi") + + processor.on_span_start(mock_span) + processor.on_span_end(mock_span) + + props = mock_client.capture_ai.call_args[1]["properties"] + assert props["$ai_input_tokens"] == 100 + assert props["$ai_cache_read_input_tokens"] == 60 + assert props["$ai_output_tokens"] == 50 + # Inclusive in output_tokens; forwarding it would be counted twice. + assert "$ai_reasoning_tokens" not in props + assert props["$ai_cache_reporting_exclusive"] is False + + def test_cached_compatible_claude_usage_is_declared_inclusive(self): + """A Claude model behind an OpenAI-compatible endpoint reports inclusive + cached tokens. Without an explicit convention, the cost backend infers + exclusive reporting from the model name and overcounts input cost.""" + from agents import set_trace_processors + from agents.tracing import generation_span, trace + + client = MagicMock(privacy_mode=False, enable_full_ai_capture=False) + set_trace_processors([]) + processor = instrument(client, distinct_id="review-user") + try: + with trace("compatible-model"): + with generation_span( + model="anthropic/claude-sonnet-4.6", + usage={ + "input_tokens": 10339, + "output_tokens": 60, + "input_tokens_details": {"cached_tokens": 10318}, + }, + ): + pass + properties = next( + call.kwargs["properties"] + for call in client.capture_ai.call_args_list + if call.kwargs["event"] == "$ai_generation" + ) + assert properties["$ai_cache_read_input_tokens"] == 10318 + assert properties.get("$ai_cache_reporting_exclusive") is False + finally: + set_trace_processors([]) + processor.shutdown() + + def test_flat_cache_keys_keep_their_own_convention( + self, processor, mock_client, mock_span + ): + """Flat cache keys come from custom models with their own convention, so + no inclusive/exclusive flag is declared for them.""" + mock_span.span_data = GenerationSpanData( + model="custom-model", + usage={ + "input_tokens": 100, + "output_tokens": 50, + "cache_read_input_tokens": 60, + }, + ) + + processor.on_span_start(mock_span) + processor.on_span_end(mock_span) + + props = mock_client.capture_ai.call_args[1]["properties"] + assert props["$ai_cache_read_input_tokens"] == 60 + assert "$ai_cache_reporting_exclusive" not in props + + def test_caller_cache_reporting_convention_takes_precedence( + self, mock_client, mock_span + ): + """A processor-wide $ai_cache_reporting_exclusive overrides the default + declared for nested OpenAI-shaped usage.""" + processor = PostHogTracingProcessor( + client=mock_client, + distinct_id="test-user", + privacy_mode=False, + properties={"$ai_cache_reporting_exclusive": True}, + ) + mock_span.span_data = GenerationSpanData( + model="gpt-5-mini", + usage={ + "input_tokens": 100, + "output_tokens": 50, + "input_tokens_details": {"cached_tokens": 60}, + }, + ) + + processor.on_span_start(mock_span) + processor.on_span_end(mock_span) + + props = mock_client.capture_ai.call_args[1]["properties"] + assert props["$ai_cache_read_input_tokens"] == 60 + assert props["$ai_cache_reporting_exclusive"] is True + def test_function_span_mapping(self, processor, mock_client, mock_span): """Test FunctionSpanData maps to $ai_span event with type=tool.""" span_data = FunctionSpanData( From 0044df957282ce76da13d2951d9982e04c4564d5 Mon Sep 17 00:00:00 2001 From: "releaser-posthog-python[bot]" <262414804+releaser-posthog-python[bot]@users.noreply.github.com> Date: Thu, 29 Oct 2026 14:02:10 +0000 Subject: [PATCH 48/60] chore: Release v7.80.3 [skip ci] (cherry picked from commit 2989cd2f36607406c6d607d7033507ca69936711) --- .sampo/changesets/openai-agents-cache-reasoning-tokens.md | 5 ----- openfeature-provider/CHANGELOG.md | 6 ++++++ openfeature-provider/pyproject.toml | 2 +- posthog/CHANGELOG.md | 6 ++++++ posthog/version.py | 2 +- pyproject.toml | 2 +- uv.lock | 4 ++-- 7 files changed, 17 insertions(+), 10 deletions(-) delete mode 100644 .sampo/changesets/openai-agents-cache-reasoning-tokens.md diff --git a/.sampo/changesets/openai-agents-cache-reasoning-tokens.md b/.sampo/changesets/openai-agents-cache-reasoning-tokens.md deleted file mode 100644 index 342cc325..00000000 --- a/.sampo/changesets/openai-agents-cache-reasoning-tokens.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -pypi/posthog: patch ---- - -Report cached input tokens from OpenAI Agents generation and response spans, which nest them under the usage details fields, and declare them inclusive. diff --git a/openfeature-provider/CHANGELOG.md b/openfeature-provider/CHANGELOG.md index 5c089f07..ee6d3fcd 100644 --- a/openfeature-provider/CHANGELOG.md +++ b/openfeature-provider/CHANGELOG.md @@ -4,6 +4,12 @@ All notable changes to `openfeature-provider-posthog` are documented here. This file is maintained by [Sampo](https://github.com/bruits/sampo) from changesets in `.sampo/changesets/` that target `pypi/openfeature-provider-posthog`. +## 0.1.134 — 2026-10-29 + +### Patch changes + +- Updated dependencies: posthog@7.80.3 + ## 0.1.133 — 2026-10-29 ### Patch changes diff --git a/openfeature-provider/pyproject.toml b/openfeature-provider/pyproject.toml index 933e1007..2cc28f28 100644 --- a/openfeature-provider/pyproject.toml +++ b/openfeature-provider/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "openfeature-provider-posthog" -version = "0.1.133" +version = "0.1.134" description = "Official PostHog provider for the OpenFeature Python SDK." readme = "README.md" authors = [{ name = "PostHog", email = "engineering@posthog.com" }] diff --git a/posthog/CHANGELOG.md b/posthog/CHANGELOG.md index 00de658b..7a8453a8 100644 --- a/posthog/CHANGELOG.md +++ b/posthog/CHANGELOG.md @@ -1,5 +1,11 @@ # posthog +## 7.80.3 — 2026-10-29 + +### Patch changes + +- [4d6e447](https://github.com/posthog/posthog-python/commit/4d6e44766175f80fbbf538ab4369b1f3916f05f7) Report cached input tokens from OpenAI Agents generation and response spans, which nest them under the usage details fields, and declare them inclusive. — Thanks @mosafariuk! + ## 7.80.2 — 2026-10-29 ### Patch changes diff --git a/posthog/version.py b/posthog/version.py index e4c192ab..0fec337c 100644 --- a/posthog/version.py +++ b/posthog/version.py @@ -1 +1 @@ -VERSION = "7.80.2" +VERSION = "7.80.3" diff --git a/pyproject.toml b/pyproject.toml index 1d54ec8a..40bddc69 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "posthog" -version = "7.80.2" +version = "7.80.3" description = "Integrate PostHog into any python application." authors = [{ name = "PostHog", email = "engineering@posthog.com" }] maintainers = [{ name = "PostHog", email = "engineering@posthog.com" }] diff --git a/uv.lock b/uv.lock index 06c5e3e0..f97bdc13 100644 --- a/uv.lock +++ b/uv.lock @@ -2656,7 +2656,7 @@ wheels = [ [[package]] name = "openfeature-provider-posthog" -version = "0.1.133" +version = "0.1.134" source = { editable = "openfeature-provider" } dependencies = [ { name = "openfeature-sdk" }, @@ -3193,7 +3193,7 @@ wheels = [ [[package]] name = "posthog" -version = "7.80.2" +version = "7.80.3" source = { editable = "." } dependencies = [ { name = "distro" }, From 8e3ad2813d827eea4b74fc4eb731281db6e29ace Mon Sep 17 00:00:00 2001 From: "releaser-posthog-python[bot]" <262414804+releaser-posthog-python[bot]@users.noreply.github.com> Date: Thu, 29 Oct 2026 14:02:15 +0000 Subject: [PATCH 49/60] Update generated references (cherry picked from commit c380dc8cc731c4d1474a2427afcd3e426bf68b88) --- .../posthog-python-references-7.80.3.json | 3548 +++++++++++++++++ .../posthog-python-references-latest.json | 2 +- 2 files changed, 3549 insertions(+), 1 deletion(-) create mode 100644 references/posthog-python-references-7.80.3.json diff --git a/references/posthog-python-references-7.80.3.json b/references/posthog-python-references-7.80.3.json new file mode 100644 index 00000000..8ee9a06c --- /dev/null +++ b/references/posthog-python-references-7.80.3.json @@ -0,0 +1,3548 @@ +{ + "id": "posthog-python", + "hogRef": "0.3", + "info": { + "version": "7.80.3", + "id": "posthog-python", + "title": "PostHog Python SDK", + "description": "Integrate PostHog into any python application.", + "slugPrefix": "posthog-python", + "specUrl": "https://github.com/PostHog/posthog-python" + }, + "types": [ + { + "id": "FeatureFlag", + "name": "FeatureFlag", + "path": "posthog.types.FeatureFlag", + "properties": [ + { + "name": "key", + "type": "str", + "description": "Field: key" + }, + { + "name": "enabled", + "type": "bool", + "description": "Field: enabled" + }, + { + "name": "variant", + "type": "Optional[str]", + "description": "Field: variant" + }, + { + "name": "reason", + "type": "Optional[FlagReason]", + "description": "Field: reason" + }, + { + "name": "metadata", + "type": "Union[FlagMetadata, LegacyFlagMetadata]", + "description": "Field: metadata" + } + ], + "example": "" + }, + { + "id": "FeatureFlagResult", + "name": "FeatureFlagResult", + "path": "posthog.types.FeatureFlagResult", + "properties": [ + { + "name": "key", + "type": "str", + "description": "Field: key" + }, + { + "name": "enabled", + "type": "bool", + "description": "Field: enabled" + }, + { + "name": "variant", + "type": "Optional[str]", + "description": "Field: variant" + }, + { + "name": "payload", + "type": "Optional[Any]", + "description": "Field: payload" + }, + { + "name": "reason", + "type": "Optional[str]", + "description": "Field: reason" + } + ], + "example": "" + }, + { + "id": "FlagMetadata", + "name": "FlagMetadata", + "path": "posthog.types.FlagMetadata", + "properties": [ + { + "name": "id", + "type": "int", + "description": "Field: id" + }, + { + "name": "payload", + "type": "Optional[str]", + "description": "Field: payload" + }, + { + "name": "version", + "type": "int", + "description": "Field: version" + }, + { + "name": "description", + "type": "str", + "description": "Field: description" + }, + { + "name": "has_experiment", + "type": "Optional[bool]", + "description": "Field: has_experiment" + } + ], + "example": "" + }, + { + "id": "FlagReason", + "name": "FlagReason", + "path": "posthog.types.FlagReason", + "properties": [ + { + "name": "code", + "type": "str", + "description": "Field: code" + }, + { + "name": "condition_index", + "type": "Optional[int]", + "description": "Field: condition_index" + }, + { + "name": "description", + "type": "str", + "description": "Field: description" + } + ], + "example": "" + }, + { + "id": "FlagsAndPayloads", + "name": "FlagsAndPayloads", + "path": "posthog.types.FlagsAndPayloads", + "properties": [ + { + "name": "featureFlags", + "type": "Optional[dict[str, Union[bool, str]]]", + "description": "Field: featureFlags" + }, + { + "name": "featureFlagPayloads", + "type": "Optional[dict[str, Any]]", + "description": "Field: featureFlagPayloads" + } + ], + "example": "" + }, + { + "id": "FlagsResponse", + "name": "FlagsResponse", + "path": "posthog.types.FlagsResponse", + "properties": [ + { + "name": "flags", + "type": "dict[str, FeatureFlag]", + "description": "Field: flags" + }, + { + "name": "errorsWhileComputingFlags", + "type": "bool", + "description": "Field: errorsWhileComputingFlags" + }, + { + "name": "requestId", + "type": "str", + "description": "Field: requestId" + }, + { + "name": "quotaLimit", + "type": "Optional[list[str]]", + "description": "Field: quotaLimit" + }, + { + "name": "evaluatedAt", + "type": "Optional[int]", + "description": "Field: evaluatedAt" + }, + { + "name": "minimalFlagCalledEvents", + "type": "bool", + "description": "Field: minimalFlagCalledEvents" + } + ], + "example": "" + }, + { + "id": "LegacyFlagMetadata", + "name": "LegacyFlagMetadata", + "path": "posthog.types.LegacyFlagMetadata", + "properties": [ + { + "name": "payload", + "type": "Any", + "description": "Field: payload" + } + ], + "example": "" + }, + { + "id": "SendFeatureFlagsOptions", + "name": "SendFeatureFlagsOptions", + "path": "posthog.types.SendFeatureFlagsOptions", + "properties": [ + { + "name": "should_send", + "type": "bool", + "description": "Field: should_send" + }, + { + "name": "only_evaluate_locally", + "type": "Optional[bool]", + "description": "Field: only_evaluate_locally" + }, + { + "name": "person_properties", + "type": "Optional[dict[str, Any]]", + "description": "Field: person_properties" + }, + { + "name": "group_properties", + "type": "Optional[dict[str, dict[str, Any]]]", + "description": "Field: group_properties" + }, + { + "name": "flag_keys_filter", + "type": "Optional[list[str]]", + "description": "Field: flag_keys_filter" + } + ], + "example": "" + }, + { + "id": "OptionalCaptureArgs", + "name": "OptionalCaptureArgs", + "path": "posthog.args.OptionalCaptureArgs", + "properties": [ + { + "name": "distinct_id", + "type": "NotRequired[Union[Number, str, UUID, int, any]]", + "description": "Field: distinct_id" + }, + { + "name": "properties", + "type": "NotRequired[Optional[dict[str, Any]]]", + "description": "Field: properties" + }, + { + "name": "timestamp", + "type": "NotRequired[Union[datetime, str, any]]", + "description": "Field: timestamp" + }, + { + "name": "uuid", + "type": "NotRequired[Union[str, UUID, any]]", + "description": "Field: uuid" + }, + { + "name": "groups", + "type": "NotRequired[Optional[dict[str, str]]]", + "description": "Field: groups" + }, + { + "name": "flags", + "type": "NotRequired[Optional[ForwardRef('FeatureFlagEvaluations')]]", + "description": "Field: flags" + }, + { + "name": "send_feature_flags", + "type": "NotRequired[Union[bool, SendFeatureFlagsOptions, any]]", + "description": "Field: send_feature_flags" + }, + { + "name": "disable_geoip", + "type": "NotRequired[Optional[bool]]", + "description": "Field: disable_geoip" + }, + { + "name": "_property_allowlist", + "type": "NotRequired[Optional[frozenset[str]]]", + "description": "Field: _property_allowlist" + } + ], + "example": "" + }, + { + "id": "OptionalSetArgs", + "name": "OptionalSetArgs", + "path": "posthog.args.OptionalSetArgs", + "properties": [ + { + "name": "distinct_id", + "type": "NotRequired[Union[Number, str, UUID, int, any]]", + "description": "Field: distinct_id" + }, + { + "name": "properties", + "type": "NotRequired[Optional[dict[str, Any]]]", + "description": "Field: properties" + }, + { + "name": "timestamp", + "type": "NotRequired[Union[datetime, str, any]]", + "description": "Field: timestamp" + }, + { + "name": "uuid", + "type": "NotRequired[Union[str, UUID, any]]", + "description": "Field: uuid" + }, + { + "name": "disable_geoip", + "type": "NotRequired[Optional[bool]]", + "description": "Field: disable_geoip" + } + ], + "example": "" + }, + { + "id": "SendFeatureFlagsOptions", + "name": "SendFeatureFlagsOptions", + "path": "posthog.types.SendFeatureFlagsOptions", + "properties": [ + { + "name": "should_send", + "type": "bool", + "description": "Field: should_send" + }, + { + "name": "only_evaluate_locally", + "type": "Optional[bool]", + "description": "Field: only_evaluate_locally" + }, + { + "name": "person_properties", + "type": "Optional[dict[str, Any]]", + "description": "Field: person_properties" + }, + { + "name": "group_properties", + "type": "Optional[dict[str, dict[str, Any]]]", + "description": "Field: group_properties" + }, + { + "name": "flag_keys_filter", + "type": "Optional[list[str]]", + "description": "Field: flag_keys_filter" + } + ], + "example": "" + } + ], + "classes": [ + { + "id": "PostHog", + "title": "PostHog", + "description": "This is the SDK reference for the PostHog Python SDK. You can learn more about example usage in the [Python SDK documentation](/docs/libraries/python). You can also follow [Flask](/docs/libraries/flask) and [Django](/docs/libraries/django) guides to integrate PostHog into your project. For long-running applications, create one client during application startup and reuse it for the lifetime of the process. This keeps background queues predictable and makes shutdown flushing straightforward. Multiple clients are still supported for intentional multi-project or multi-host setups.", + "functions": [ + { + "id": "__init__", + "title": "Client", + "description": "Initialize a new PostHog client instance.", + "details": "", + "category": "Initialization", + "params": [ + { + "name": "project_api_key", + "description": "PostHog project API key/token.", + "isOptional": true, + "type": "str" + }, + { + "name": "host", + "description": "PostHog host. Defaults to the US ingestion endpoint when not set. App hosts such as ``https://us.posthog.com`` are mapped to the corresponding ingestion host.", + "isOptional": false, + "type": "any" + }, + { + "name": "debug", + "description": "Enable verbose SDK logging and re-raise errors from public API methods.", + "isOptional": false, + "type": "bool" + }, + { + "name": "max_queue_size", + "description": "Maximum number of events buffered before upload.", + "isOptional": false, + "type": "int" + }, + { + "name": "send", + "description": "If False, queueing succeeds but events are not sent.", + "isOptional": false, + "type": "bool" + }, + { + "name": "on_error", + "description": "Optional callback invoked by background consumers when an upload fails. Keep it short and non-blocking. Calling lifecycle methods directly is safe and deferred, but do not start another thread or task that calls ``flush()``, ``join()``, or ``shutdown()`` and then wait for it from the callback.", + "isOptional": false, + "type": "any" + }, + { + "name": "flush_at", + "description": "Number of queued events that triggers a batch upload.", + "isOptional": false, + "type": "int" + }, + { + "name": "flush_interval", + "description": "Maximum seconds a background consumer waits before flushing a partial batch.", + "isOptional": false, + "type": "float" + }, + { + "name": "gzip", + "description": "Whether to gzip event upload payloads.", + "isOptional": false, + "type": "bool" + }, + { + "name": "max_retries", + "description": "Number of upload retries. Values below 0 are treated as 0.", + "isOptional": false, + "type": "int" + }, + { + "name": "sync_mode", + "description": "If True, send each event synchronously instead of using background worker threads. This blocks the calling thread; in asyncio applications such as FastAPI, use ``AsyncPosthog`` instead.", + "isOptional": false, + "type": "bool" + }, + { + "name": "timeout", + "description": "HTTP request timeout in seconds for event uploads.", + "isOptional": false, + "type": "float" + }, + { + "name": "thread", + "description": "Number of background consumer threads.", + "isOptional": false, + "type": "int" + }, + { + "name": "poll_interval", + "description": "Seconds between local feature flag definition refreshes.", + "isOptional": false, + "type": "int" + }, + { + "name": "personal_api_key", + "description": "Deprecated alias for ``secret_key``. Still honored for backwards compatibility; prefer ``secret_key``, which also accepts a Project Secret API Key.", + "isOptional": false, + "type": "any" + }, + { + "name": "disabled", + "description": "If True, disable captures and API requests. Useful in tests.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable server-side GeoIP enrichment. Defaults to True.", + "isOptional": false, + "type": "bool" + }, + { + "name": "is_server", + "description": "Whether events are emitted from a server-side runtime. Defaults to True; set to False when using the SDK as a client/CLI so the device OS is attributed to the person normally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "historical_migration", + "description": "Mark events as historical migration imports.", + "isOptional": false, + "type": "bool" + }, + { + "name": "feature_flags_request_timeout_seconds", + "description": "Timeout in seconds for feature flag and remote config requests.", + "isOptional": false, + "type": "float" + }, + { + "name": "feature_flags_request_max_retries", + "description": "Number of retries for feature flag requests after network, transport, or timeout failures. Defaults to 1. Set to 0 to disable retries.", + "isOptional": false, + "type": "int" + }, + { + "name": "super_properties", + "description": "Properties merged into every captured event.", + "isOptional": false, + "type": "any" + }, + { + "name": "enable_exception_autocapture", + "description": "Automatically capture uncaught exceptions.", + "isOptional": false, + "type": "bool" + }, + { + "name": "log_captured_exceptions", + "description": "Also log exceptions captured by error tracking.", + "isOptional": false, + "type": "bool" + }, + { + "name": "project_root", + "description": "Root path used to determine in-app stack frames for captured exceptions. Defaults to the current working directory.", + "isOptional": false, + "type": "any" + }, + { + "name": "privacy_mode", + "description": "For AI observability, capture usage metadata without prompt inputs or outputs.", + "isOptional": false, + "type": "bool" + }, + { + "name": "before_send", + "description": "Optional callback that can modify or drop events before upload. Return ``None`` to drop an event.", + "isOptional": false, + "type": "any" + }, + { + "name": "flag_fallback_cache_url", + "description": "Optional feature flag fallback cache URL, such as ``memory://local/?ttl=300&size=10000`` or a Redis URL.", + "isOptional": false, + "type": "any" + }, + { + "name": "enable_local_evaluation", + "description": "Whether to poll feature flag definitions for local evaluation when a personal API key is configured.", + "isOptional": false, + "type": "bool" + }, + { + "name": "flag_definition_cache_provider", + "description": "Optional external cache provider for sharing feature flag definitions across workers.", + "isOptional": true, + "type": "FlagDefinitionCacheProvider" + }, + { + "name": "capture_exception_code_variables", + "description": "Capture local variable values on exception stack frames.", + "isOptional": false, + "type": "bool" + }, + { + "name": "code_variables_mask_patterns", + "description": "Variable-name patterns to mask when capturing code variables.", + "isOptional": false, + "type": "any" + }, + { + "name": "code_variables_ignore_patterns", + "description": "Variable-name patterns to omit when capturing code variables.", + "isOptional": false, + "type": "any" + }, + { + "name": "code_variables_mask_url_credentials", + "description": "Scrub credentials embedded in URLs/DSNs (e.g. ``user:pass@host``) from captured code variables, regardless of the surrounding variable name. Defaults to True.", + "isOptional": false, + "type": "any" + }, + { + "name": "code_variables_detect_secrets", + "description": "Last-resort entropy-based detection that redacts high-entropy secret-looking values (API keys, tokens, strong passwords) sitting in innocuously-named variables, after the name and URL checks. Skips structured ids (UUIDs, ObjectIds, hashes). Defaults to True.", + "isOptional": false, + "type": "any" + }, + { + "name": "in_app_modules", + "description": "Module/package prefixes treated as in-app frames in captured exceptions.", + "isOptional": false, + "type": "UnionType[list[str], any]" + }, + { + "name": "enable_exception_autocapture_rate_limiting", + "description": "Rate limit autocaptured exceptions client-side with a token bucket per exception type. Disabled by default.", + "isOptional": false, + "type": "bool" + }, + { + "name": "exception_autocapture_bucket_size", + "description": "Maximum burst of autocaptured exceptions allowed per exception type (token bucket size, clamped to 0-100).", + "isOptional": false, + "type": "int" + }, + { + "name": "exception_autocapture_refill_rate", + "description": "Tokens restored per refill interval for each exception type's bucket.", + "isOptional": false, + "type": "int" + }, + { + "name": "exception_autocapture_refill_interval_seconds", + "description": "Seconds between token refills for autocaptured exception rate limiting.", + "isOptional": false, + "type": "int" + }, + { + "name": "capture_mode", + "description": "Capture wire protocol to use. Defaults to ``CaptureMode.V0`` (legacy ``/batch/``). Set ``CaptureMode.V1`` (or pass the string ``\"v1\"``) to opt into ``/i/v1/analytics/events``. When omitted, the ``POSTHOG_CAPTURE_MODE`` env var is consulted, then ``V0``.", + "isOptional": false, + "type": "CaptureMode" + }, + { + "name": "capture_compression", + "description": "Request-body compression for capture-v1 uploads (ignored in V0, which uses ``gzip``). ``CaptureCompression.GZIP`` or ``DEFLATE`` (or the strings ``\"gzip\"``/``\"deflate\"``). When omitted, the ``POSTHOG_CAPTURE_COMPRESSION`` env var is consulted, then the legacy ``gzip`` flag, then no compression.", + "isOptional": false, + "type": "CaptureCompression" + }, + { + "name": "secret_key", + "description": "A Personal API Key or Project Secret API Key, used to authenticate local feature flag evaluation, remote config payloads, and decrypted flag payloads. Example:: posthog.Client(project_api_key, secret_key=\"phx_...\")", + "isOptional": false, + "type": "any" + }, + { + "name": "metrics", + "description": "", + "isOptional": true, + "type": "dict" + }, + { + "name": "enable_full_ai_capture", + "description": "Route PostHog AI wrapper events through the dedicated AI capture endpoint and capture full AI content: skips string truncation and passes media (base64/data URIs) through unredacted. ``privacy_mode`` always wins. Defaults to False.", + "isOptional": false, + "type": "bool" + }, + { + "name": "capture_trace_context", + "description": "When OpenTelemetry is installed and a valid span is active at capture time, add its trace and span IDs as ``$trace_id`` and ``$span_id`` properties to events captured with ``capture()`` and ``capture_ai()``, so they can be correlated with backend traces. Explicit ``$trace_id``/``$span_id`` values passed in ``properties`` win. Exception events (``capture_exception``) always attach these IDs regardless of this setting. Defaults to False.", + "isOptional": false, + "type": "bool" + }, + { + "name": "_use_ai_lane", + "description": "", + "isOptional": false, + "type": "bool" + }, + { + "name": "_enable_multimodal_capture", + "description": "", + "isOptional": false, + "type": "bool" + }, + { + "name": "traces", + "description": "Config dict for distributed tracing: ``service_name``, ``service_version``, ``environment``, ``resource_attributes``, ``flush_interval`` (5 s), ``max_queue_size`` (2048), ``max_export_batch_size`` (512), ``max_live_spans`` (10000), ``max_span_age`` (3600 s), ``max_attributes_per_span`` (128), ``max_events_per_span`` (128), ``max_attribute_value_length`` (8192). ``before_span_send`` is a callable, or a list run in order, that receives each finished span as a dict (``trace_id``, ``span_id`` and ``parent_span_id`` are read-only) and returns it, edited, or ``None`` to drop it; a hook that raises drops the span. Tracing is off until this is provided. Spans export on a background timer even with ``sync_mode``; serverless handlers should call ``flush()`` before returning. Defaults to None.", + "isOptional": true, + "type": "dict" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import Posthog\n\nposthog = Posthog('', host='')" + } + ] + }, + { + "id": "alias", + "title": "alias", + "description": "Create an alias between two distinct IDs.", + "details": "", + "category": "Identification", + "params": [ + { + "name": "previous_id", + "description": "The previous distinct ID. Required - the call is dropped with a warning if it is missing or empty.", + "isOptional": true, + "type": "Number" + }, + { + "name": "distinct_id", + "description": "The new distinct ID to alias to. Falls back to the context distinct ID; the call is dropped with a warning if neither is available.", + "isOptional": true, + "type": "str" + }, + { + "name": "timestamp", + "description": "The timestamp of the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC.", + "isOptional": false, + "type": "datetime" + }, + { + "name": "uuid", + "description": "A unique identifier for the event. If provided, it must be a valid UUID string or uuid.UUID instance; invalid values are ignored and replaced with a newly generated UUID.", + "isOptional": true, + "type": "str" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this event.", + "isOptional": true, + "type": "bool" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.alias(previous_id='distinct_id', distinct_id='alias_id')" + } + ] + }, + { + "id": "capture", + "title": "capture", + "description": "Captures an event manually. [Learn about capture best practices](https://posthog.com/docs/product-analytics/capture-events)", + "details": "", + "category": "Capture", + "params": [ + { + "name": "event", + "description": "The event name to capture.", + "isOptional": true, + "type": "str" + }, + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalCaptureArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Anonymous event", + "code": "# Anonymous event\nposthog.capture('some-anon-event')" + }, + { + "id": "example_2", + "name": "Context usage", + "code": "# Context usage\nfrom posthog import identify_context, new_context\nwith new_context():\n identify_context('distinct_id_of_the_user')\n posthog.capture('user_signed_up')\n posthog.capture('user_logged_in')\n posthog.capture('some-custom-action', distinct_id='distinct_id_of_the_user')" + }, + { + "id": "example_3", + "name": "Set event properties", + "code": "# Set event properties\nposthog.capture(\n \"user_signed_up\",\n distinct_id=\"distinct_id_of_the_user\",\n properties={\n \"login_type\": \"email\",\n \"is_free_trial\": \"true\"\n }\n)" + }, + { + "id": "example_4", + "name": "Page view event", + "code": "# Page view event\nposthog.capture('$pageview', distinct_id=\"distinct_id_of_the_user\", properties={'$current_url': 'https://example.com'})" + } + ] + }, + { + "id": "capture_ai", + "title": "capture_ai", + "description": "Capture an AI event on the dedicated AI capture endpoint. Beta: the signature is stable; operational limits (per-event size cap, batching, endpoint) may change without notice. Takes the same arguments and returns the same value as `capture()`: the event UUID, or None when the event was not admitted (disabled client, or dropped by `before_send`). The event is queued on an isolated AI lane with its own consumer pool and a higher per-event size cap, posting to the dedicated AI ingestion endpoint. The payload is sent as given \u2014 no redaction or truncation is applied here.", + "details": "", + "category": "Capture", + "params": [ + { + "name": "event", + "description": "", + "isOptional": true, + "type": "str" + }, + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalCaptureArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + } + }, + { + "id": "capture_exception", + "title": "capture_exception", + "description": "Capture an exception for error tracking. When OpenTelemetry is installed and a valid span is active, its trace and span IDs are added as ``$trace_id`` and ``$span_id`` event properties.", + "details": "", + "category": "Error Tracking", + "params": [ + { + "name": "exception", + "description": "The exception to capture.", + "isOptional": true, + "type": "BaseException" + }, + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalCaptureArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "try:\n # Some code that might fail\n pass\nexcept Exception as e:\n posthog.capture_exception(e, 'user_distinct_id', properties=additional_properties)" + } + ] + }, + { + "id": "evaluate_flags", + "title": "evaluate_flags", + "description": "Evaluate all feature flags for a user in a single call and return a :class:`FeatureFlagEvaluations` snapshot. Branch on ``.is_enabled()`` / ``.get_flag()`` and pass the same snapshot to :meth:`capture` via the ``flags`` option so events carry the exact flag values the code branched on. Prefer this over repeated ``get_feature_flag()`` calls and over ``capture(send_feature_flags=True)`` \u2014 it consolidates flag evaluation into a single ``/flags`` request per incoming request. Local evaluation is transparent: when the poller resolves a flag, the snapshot's ``$feature_flag_called`` events are tagged ``locally_evaluated=True`` and reason ``\"Evaluated locally\"``.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The user's distinct ID. If ``None``, falls back to the context distinct_id. If still unresolvable, returns an empty snapshot.", + "isOptional": false, + "type": "Number" + }, + { + "name": "groups", + "description": "Mapping of group type to group key.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "Person properties to use for evaluation.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "Group properties keyed by group type.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "If True, never fall back to remote evaluation \u2014 flags that can't be evaluated locally are omitted from the snapshot and listed with a reason in its ``unresolved_flags``.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup.", + "isOptional": true, + "type": "bool" + }, + { + "name": "flag_keys", + "description": "Optional list that scopes local evaluation, the underlying ``/flags`` request, and the returned snapshot. When omitted or ``None``, all flags are evaluated. An empty list returns an empty snapshot without evaluating flags. A requested key absent from loaded local definitions is included in one remote fallback per ``evaluate_flags`` call unless ``only_evaluate_locally`` is True. If the server also does not know the key, it is omitted from the snapshot.", + "isOptional": true, + "type": "list[str]" + }, + { + "name": "device_id", + "description": "Optional device ID override. If not provided, falls back to the context device_id (which may be set via tracing headers). Used by experience-continuity flags to match users across distinct_id changes.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "FeatureFlagEvaluations" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "flags = posthog.evaluate_flags(\n \"user_123\",\n person_properties={\"plan\": \"enterprise\"},\n)\nif flags.is_enabled(\"new-dashboard\"):\n render_new_dashboard()\nposthog.capture(\"page_viewed\", distinct_id=\"user_123\", flags=flags)" + } + ] + }, + { + "id": "feature_enabled", + "title": "feature_enabled", + "description": "Check if a feature flag is enabled for a user.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "The feature flag key.", + "isOptional": true, + "type": "str" + }, + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to only evaluate locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "send_feature_flag_events", + "description": "Whether to send feature flag events.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[bool]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "is_my_flag_enabled = posthog.feature_enabled('flag-key', 'distinct_id_of_your_user')\nif is_my_flag_enabled:\n # Do something differently for this user\n # Optional: fetch the payload\n matched_flag_payload = posthog.get_feature_flag_payload('flag-key', 'distinct_id_of_your_user')" + } + ] + }, + { + "id": "feature_flag_definitions", + "title": "feature_flag_definitions", + "description": "Return feature flag definitions loaded for local evaluation. Returns: The currently loaded feature flag definitions, or ``None`` before local evaluation has loaded definitions.", + "details": "", + "category": "Feature flags", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "flush", + "title": "flush", + "description": "Attempt immediate delivery of queued events, ended spans and autocaptured metrics, keeping the client reusable. For a client shared across requests or serverless invocations (including the module-level client), call ``flush()`` before a short-lived invocation returns. Use ``shutdown()`` instead only for final process cleanup or when disposing of a client that will not be reused. Flush does not stop the background workers or feature flag poller, and does not end open spans. Returning does not guarantee server receipt: pending work can remain after a timeout or span export failure, and failed events may be dropped by the consumers. Calls made directly from SDK callbacks such as ``on_error`` are deferred rather than blocking the worker that invoked the callback.", + "details": "", + "category": null, + "params": [ + { + "name": "timeout_seconds", + "description": "Seconds to wait for event queues to drain, shared across event lanes. Defaults to 10 seconds. Pass ``None`` to wait without a time limit; this does not guarantee successful delivery. Queued spans flush concurrently. The span exporter waits up to this many seconds for an in-flight flush, then uses a fresh drain budget once it acquires the lock. If the lock wait times out, the in-flight flush is left to finish. At least one span request is attempted after acquiring the lock, even with no budget left; retriable failures are retried after backoff while budget remains, with one last attempt at the deadline. Each span request is bounded by the client's ``timeout`` in seconds, and the last request is not cut short. This is therefore not a strict wall-clock limit for the whole call. With ``None``, a retriable span failure is left for a later flush rather than retried within this call.", + "isOptional": true, + "type": "float" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.capture('event_name', distinct_id='user_id')\n# Before a serverless invocation returns; keep the shared client open.\nposthog.flush(timeout_seconds=10)" + } + ] + }, + { + "id": "get_active_span", + "title": "get_active_span", + "description": "The span that is active in the current context, or ``None``. Alpha. Only entering a span (``with posthog.start_span(...) as span:``) makes it active; a span started manually is not. Use it to propagate the trace to the next service: ``span.traceparent()`` is the header value.", + "details": "", + "category": "Tracing", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[Span]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "span = posthog.get_active_span()\nif span is not None:\n headers[\"traceparent\"] = span.traceparent()" + } + ] + }, + { + "id": "get_all_flags", + "title": "get_all_flags", + "description": "Get all feature flags for a user.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to only evaluate locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "flag_keys_to_evaluate", + "description": "A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance.", + "isOptional": true, + "type": "list[str]" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[dict[str, Union[bool, str]]]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.get_all_flags('distinct_id_of_your_user')" + } + ] + }, + { + "id": "get_all_flags_and_payloads", + "title": "get_all_flags_and_payloads", + "description": "Get all feature flags and their payloads for a user.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to only evaluate locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "flag_keys_to_evaluate", + "description": "A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance.", + "isOptional": true, + "type": "list[str]" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "FlagsAndPayloads" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.get_all_flags_and_payloads('distinct_id_of_your_user')" + } + ] + }, + { + "id": "get_feature_flag", + "title": "get_feature_flag", + "description": "Get multivariate feature flag value for a user.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "The feature flag key.", + "isOptional": true, + "type": "str" + }, + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to only evaluate locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "send_feature_flag_events", + "description": "Whether to send feature flag events.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Union[bool, str, any]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "enabled_variant = posthog.get_feature_flag('flag-key', 'distinct_id_of_your_user')\nif enabled_variant == 'variant-key': # replace 'variant-key' with the key of your variant\n # Do something differently for this user\n # Optional: fetch the payload\n matched_flag_payload = posthog.get_feature_flag_payload('flag-key', 'distinct_id_of_your_user')" + } + ] + }, + { + "id": "get_feature_flag_evaluation_runtime", + "title": "get_feature_flag_evaluation_runtime", + "description": "Return where a locally loaded feature flag is meant to be evaluated.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "The feature flag key.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[FeatureFlagEvaluationRuntime]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import FeatureFlagEvaluationRuntime\n\nruntime = posthog.get_feature_flag_evaluation_runtime(\"my-flag\")\nif runtime is FeatureFlagEvaluationRuntime.SERVER:\n ..." + } + ] + }, + { + "id": "get_feature_flag_keys_by_evaluation_runtime", + "title": "get_feature_flag_keys_by_evaluation_runtime", + "description": "Return the keys of locally loaded flags that a runtime can evaluate. A flag set to ``FeatureFlagEvaluationRuntime.ALL`` suits either runtime, so it is returned for ``CLIENT`` and for ``SERVER``, and asking for ``ALL`` returns every loaded flag. Use this to decide which flags to hand to a browser when a backend serves flags to its own frontend.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "evaluation_runtime", + "description": "The runtime to match, as a ``FeatureFlagEvaluationRuntime`` or its string value.", + "isOptional": true, + "type": "FeatureFlagEvaluationRuntime" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "list[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import FeatureFlagEvaluationRuntime\n\nclient_keys = posthog.get_feature_flag_keys_by_evaluation_runtime(\n FeatureFlagEvaluationRuntime.CLIENT\n)" + } + ] + }, + { + "id": "get_feature_flag_payload", + "title": "get_feature_flag_payload", + "description": "Get the payload for a feature flag.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "The feature flag key.", + "isOptional": true, + "type": "str" + }, + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "match_value", + "description": "The specific flag value to get payload for.", + "isOptional": false, + "type": "bool" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to only evaluate locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "send_feature_flag_events", + "description": "Deprecated. Use get_feature_flag() instead if you need events.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[object]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "is_my_flag_enabled = posthog.feature_enabled('flag-key', 'distinct_id_of_your_user')\n\nif is_my_flag_enabled:\n # Do something differently for this user\n # Optional: fetch the payload\n matched_flag_payload = posthog.get_feature_flag_payload('flag-key', 'distinct_id_of_your_user')" + } + ] + }, + { + "id": "get_feature_flag_result", + "title": "get_feature_flag_result", + "description": "Get a FeatureFlagResult object which contains the flag result and payload for a key by evaluating locally or remotely depending on whether local evaluation is enabled and the flag can be locally evaluated. This also captures the `$feature_flag_called` event unless `send_feature_flag_events` is `False`.", + "details": "", + "category": null, + "params": [ + { + "name": "key", + "description": "The feature flag key.", + "isOptional": true, + "type": "str" + }, + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to only evaluate locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "send_feature_flag_events", + "description": "Whether to send feature flag events.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[FeatureFlagResult]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "flag_result = posthog.get_feature_flag_result('flag-key', 'distinct_id_of_your_user')\nif flag_result and flag_result.get_value() == 'variant-key':\n # Do something differently for this user\n # Optional: fetch the payload\n matched_flag_payload = flag_result.payload" + } + ] + }, + { + "id": "get_feature_flags_and_payloads", + "title": "get_feature_flags_and_payloads", + "description": "Get feature flags and payloads for a user.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "flag_keys_to_evaluate", + "description": "A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance.", + "isOptional": true, + "type": "list[str]" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "FlagsAndPayloads" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "result = posthog.get_feature_flags_and_payloads('')" + } + ] + }, + { + "id": "get_feature_payloads", + "title": "get_feature_payloads", + "description": "Get feature flag payloads for a user, preserving valid serialized JSON.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "flag_keys_to_evaluate", + "description": "A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance.", + "isOptional": true, + "type": "list[str]" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "dict[str, Optional[str]]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "payloads = posthog.get_feature_payloads('')" + } + ] + }, + { + "id": "get_feature_variants", + "title": "get_feature_variants", + "description": "Get feature flag variants for a user.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "flag_keys_to_evaluate", + "description": "A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance.", + "isOptional": true, + "type": "list[str]" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "dict[str, Union[bool, str]]" + } + }, + { + "id": "get_flags_decision", + "title": "get_flags_decision", + "description": "Get feature flags decision.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The distinct ID of the user.", + "isOptional": false, + "type": "Number" + }, + { + "name": "groups", + "description": "A dictionary of group information.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "A dictionary of person properties.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "A dictionary of group properties.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this request.", + "isOptional": true, + "type": "bool" + }, + { + "name": "flag_keys_to_evaluate", + "description": "A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance.", + "isOptional": true, + "type": "list[str]" + }, + { + "name": "device_id", + "description": "The device ID for this request.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "FlagsResponse" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "decision = posthog.get_flags_decision('user123')" + } + ] + }, + { + "id": "get_remote_config_payload", + "title": "get_remote_config_payload", + "description": "Get the payload for a remote config feature flag.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "The remote config feature flag key.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "get_tags", + "title": "get_tags", + "description": "Get all tags from the current context. Returns: Dict of all tags in the current context.", + "details": "", + "category": "Contexts", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "dict[str, Any]" + } + }, + { + "id": "group_identify", + "title": "group_identify", + "description": "Identify a group and set its properties.", + "details": "", + "category": "Identification", + "params": [ + { + "name": "group_type", + "description": "The type of group (e.g., 'company', 'team'). Required - the call is dropped with a warning if it is missing or empty.", + "isOptional": true, + "type": "str" + }, + { + "name": "group_key", + "description": "The unique identifier for the group. Required - the call is dropped with a warning if it is missing or empty.", + "isOptional": true, + "type": "str" + }, + { + "name": "properties", + "description": "A dictionary of properties to set on the group.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "timestamp", + "description": "The timestamp of the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC.", + "isOptional": false, + "type": "datetime" + }, + { + "name": "uuid", + "description": "A unique identifier for the event. If provided, it must be a valid UUID string or uuid.UUID instance; invalid values are ignored and replaced with a newly generated UUID.", + "isOptional": false, + "type": "str" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP for this event.", + "isOptional": true, + "type": "bool" + }, + { + "name": "distinct_id", + "description": "The distinct ID of the user performing the action.", + "isOptional": false, + "type": "Number" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.group_identify('company', 'company_id_in_your_db', {\n 'name': 'Awesome Inc.',\n 'employees': 11\n})" + } + ] + }, + { + "id": "identify_context", + "title": "identify_context", + "description": "Identify the current context with a distinct ID.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "distinct_id", + "description": "The distinct ID to associate with the current context and its children.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + } + }, + { + "id": "join", + "title": "join", + "description": "Attempt to process queued events and end the consumer threads. Do not use directly, call `shutdown()` instead. Failed or undrainable events may be dropped and reported through logging or ``on_error``; returning does not guarantee server receipt. Lifecycle cleanup is attempted once, and cleanup failures are logged without retry.", + "details": "", + "category": null, + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.join()" + } + ] + }, + { + "id": "load_feature_flags", + "title": "load_feature_flags", + "description": "Load feature flags for local evaluation.", + "details": "", + "category": "Feature flags", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.load_feature_flags()" + } + ] + }, + { + "id": "new_context", + "title": "new_context", + "description": "Create a new context for managing shared state. Learn more about [contexts](/docs/libraries/python#contexts).", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "fresh", + "description": "Whether to create a fresh context that doesn't inherit from parent.", + "isOptional": false, + "type": "bool" + }, + { + "name": "capture_exceptions", + "description": "Whether to automatically capture exceptions in this context. If omitted, defaults to this client's exception autocapture setting.", + "isOptional": true, + "type": "bool" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "with client.new_context():\n client.identify_context('')\n client.capture('event_name')" + } + ] + }, + { + "id": "scoped", + "title": "scoped", + "description": "Decorator that creates a new context for the wrapped function using this client.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "fresh", + "description": "Whether to create a fresh context that doesn't inherit from parent.", + "isOptional": false, + "type": "bool" + }, + { + "name": "capture_exceptions", + "description": "Whether to automatically capture exceptions in this context. If omitted, defaults to this client's exception autocapture setting.", + "isOptional": true, + "type": "bool" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "set", + "title": "set", + "description": "Set properties on a person profile.", + "details": "", + "category": "Identification", + "params": [ + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalSetArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Set with distinct id", + "code": "# Set with distinct id\nposthog.set(distinct_id='user123', properties={'name': 'Max Hedgehog'})" + } + ] + }, + { + "id": "set_context_device_id", + "title": "set_context_device_id", + "description": "Set the device ID for the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "device_id", + "description": "The device ID to associate with the current context and its children.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + } + }, + { + "id": "set_context_session", + "title": "set_context_session", + "description": "Set the session ID for the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "session_id", + "description": "The session ID to associate with the current context and its children.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + } + }, + { + "id": "set_once", + "title": "set_once", + "description": "Set properties on a person profile only if they haven't been set before.", + "details": "", + "category": "Identification", + "params": [ + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalSetArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.set_once(distinct_id='user123', properties={'initial_signup_date': '2024-01-01'})" + } + ] + }, + { + "id": "shutdown", + "title": "shutdown", + "description": "Attempt final delivery and permanently shut down the client. Call this for final process cleanup or when disposing of a client that will not be reused. It closes capture lanes, stops background workers and the feature flag poller, and tears down integrations. Do not call it at the end of each request or serverless invocation when the client is shared; call ``flush()`` before returning instead. A shut-down client cannot be reused; create a new client if needed. Normally this method blocks until queued events have been attempted and cleanup finishes. Failed or undrainable events may be dropped and reported through logging or ``on_error``; returning does not guarantee server receipt. Event draining has no timeout. Queued spans get one final flush with up to 30 seconds waiting for an in-flight flush, then a fresh 30-second drain budget; the last HTTP request is not cut short. Any spans it cannot send are discarded with a warning, as are spans still open. Lifecycle cleanup is attempted once, and cleanup failures are logged without retry. When called directly from an SDK callback such as ``on_error``, shutdown is deferred to avoid blocking the worker that invoked the callback. If the callback must coordinate a blocking shutdown, have it signal an application-owned thread and return before that thread calls shutdown. Do not wait inside the callback for another thread or task that calls a lifecycle method.", + "details": "", + "category": null, + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog.shutdown()" + } + ] + }, + { + "id": "start_span", + "title": "start_span", + "description": "Start a span for distributed tracing. Alpha. Returns a span handle. Use it as a context manager to make it the active span for the block and end it on exit (recording a raised exception on the way out); or call ``end()`` yourself for a span that cannot wrap a block. Spans started inside the block nest under it automatically. Always returns a usable handle, even when tracing is off, so calling code never branches.", + "details": "", + "category": "Tracing", + "params": [ + { + "name": "name", + "description": "A low-cardinality operation name, e.g. ``GET /users/:id``. Variable values belong in attributes, not the name.", + "isOptional": true, + "type": "str" + }, + { + "name": "kind", + "description": "``internal`` (default), ``server``, ``client``, ``producer`` or ``consumer``.", + "isOptional": true, + "type": "str" + }, + { + "name": "attributes", + "description": "Initial attributes.", + "isOptional": true, + "type": "Mapping[str, Any]" + }, + { + "name": "parent", + "description": "A span handle, or an inbound W3C ``traceparent`` header value to continue a remote trace. Defaults to the active span. A forked child starts with no active span; pass the parent span to continue a trace across a fork.", + "isOptional": false, + "type": "Span" + }, + { + "name": "tracestate", + "description": "The inbound ``tracestate`` header accompanying a ``traceparent`` string ``parent``; preserved and propagated.", + "isOptional": true, + "type": "str" + }, + { + "name": "start_time", + "description": "A ``datetime`` or epoch seconds, to backdate the span.", + "isOptional": false, + "type": "datetime" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Span" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "posthog = Posthog(\"\", traces={\"service_name\": \"checkout-api\"})\n\nwith posthog.start_span(\"POST /checkout\", parent=request.headers.get(\"traceparent\")) as span:\n span.set_attribute(\"plan\", user.plan)\n with posthog.start_span(\"db.query\", kind=\"client\"):\n ...\n outgoing_headers = {\"traceparent\": span.traceparent()}" + } + ] + }, + { + "id": "tag", + "title": "tag", + "description": "Add a tag to the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "name", + "description": "The tag key.", + "isOptional": true, + "type": "str" + }, + { + "name": "value", + "description": "The tag value.", + "isOptional": true, + "type": "Any" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + } + } + ] + }, + { + "id": "PostHogModule", + "title": "PostHog Module Functions", + "description": "Global functions available in the PostHog module", + "functions": [ + { + "id": "alias", + "title": "alias", + "description": "Associate user behaviour before and after they e.g. register, login, or perform some other identifying action.", + "details": "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?\". Particularly useful for associating user behaviour before and after they e.g. register, login, or perform some other identifying action.", + "category": "Identification", + "params": [ + { + "name": "previous_id", + "description": "The unique ID of the user before", + "isOptional": true, + "type": "Number" + }, + { + "name": "distinct_id", + "description": "The current unique id", + "isOptional": true, + "type": "str" + }, + { + "name": "timestamp", + "description": "Optional timestamp for the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC.", + "isOptional": false, + "type": "datetime" + }, + { + "name": "uuid", + "description": "Optional UUID for the event", + "isOptional": true, + "type": "str" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup", + "isOptional": true, + "type": "bool" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Alias user", + "code": "# Alias user\nfrom posthog import alias\nalias(previous_id='distinct_id', distinct_id='alias_id')" + } + ] + }, + { + "id": "capture", + "title": "capture", + "description": "Capture anything a user does within your system.", + "details": "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. A capture call requires an event name to specify the event. We recommend using [verb] [noun], like `movie played` or `movie updated` to easily identify what your events mean later on. Capture takes a number of optional arguments, which are defined by the `OptionalCaptureArgs` type.", + "category": "Events", + "params": [ + { + "name": "event", + "description": "The event name to specify the event **kwargs: Optional arguments including:", + "isOptional": true, + "type": "str" + }, + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalCaptureArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Context and capture usage", + "code": "# Context and capture usage\nfrom posthog import new_context, identify_context, tag_context, capture\n# Enter a new context (e.g. a request/response cycle, an instance of a background job, etc)\nwith new_context():\n # Associate this context with some user, by distinct_id\n identify_context('some user')\n\n # Capture an event, associated with the context-level distinct ID ('some user')\n capture('movie started')\n\n # Capture an event associated with some other user (overriding the context-level distinct ID)\n capture('movie joined', distinct_id='some-other-user')\n\n # Capture an event with some properties\n capture('movie played', properties={'movie_id': '123', 'category': 'romcom'})\n\n # Capture an event with some properties\n capture('purchase', properties={'product_id': '123', 'category': 'romcom'})\n # Capture an event with some associated group\n capture('purchase', groups={'company': 'id:5'})\n\n # Adding a tag to the current context will cause it to appear on all subsequent events\n tag_context('some-tag', 'some-value')\n\n capture('another-event') # Will be captured with `'some-tag': 'some-value'` in the properties dict" + }, + { + "id": "example_2", + "name": "Set event properties", + "code": "# Set event properties\nfrom posthog import capture\ncapture(\n \"user_signed_up\",\n distinct_id=\"distinct_id_of_the_user\",\n properties={\n \"login_type\": \"email\",\n \"is_free_trial\": \"true\"\n }\n)" + } + ] + }, + { + "id": "capture_ai", + "title": "capture_ai", + "description": "Capture an AI event on the dedicated AI capture endpoint. Beta: the signature is stable; operational limits (per-event size cap, batching, endpoint) may change without notice. Takes the same arguments and returns the same value as `capture()`: the event UUID, or None when the event was not admitted (disabled client, or dropped by `before_send`). The event is delivered on an isolated queue with its own consumer pool and a higher per-event size cap, posting to the dedicated AI ingestion endpoint. The payload is sent as given \u2014 no redaction or truncation is applied here.", + "details": "", + "category": "Events", + "params": [ + { + "name": "event", + "description": "The event name, normally one of the `$ai_*` event names. **kwargs: Same optional arguments as `capture()`.", + "isOptional": true, + "type": "str" + }, + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalCaptureArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import capture_ai\n\nuuid = capture_ai(\n \"$ai_generation\",\n distinct_id=\"user_123\",\n properties={\"$ai_model\": \"gpt-5\"},\n)" + } + ] + }, + { + "id": "capture_exception", + "title": "capture_exception", + "description": "Capture exceptions that happen in your code.", + "details": "Capture exception is idempotent - if it is called twice with the same exception instance, only a occurrence will be tracked in posthog. This is because, generally, contexts will cause exceptions to be captured automatically. However, to ensure you track an exception, if you catch and do not re-raise it, capturing it manually is recommended, unless you are certain it will have crossed a context boundary (e.g. by existing a `with posthog.new_context():` block already). If the passed exception was raised and caught, the captured stack trace will consist of every frame between where the exception was raised and the point at which it is captured (the \"traceback\"). If the passed exception was never raised, e.g. if you call `posthog.capture_exception(ValueError(\"Some Error\"))`, the stack trace captured will be the full stack trace at the moment the exception was captured. Note that heavy use of contexts will lead to truncated stack traces, as the exception will be captured by the context entered most recently, 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. `capture_exception` takes the same set of optional arguments as `capture`.", + "category": "Events", + "params": [ + { + "name": "exception", + "description": "The exception to capture. If not provided, the current exception is captured via `sys.exc_info()` **kwargs: Optional capture arguments including distinct_id, properties, timestamp, uuid, groups, flags, send_feature_flags, and disable_geoip. Overriding reserved exception properties through ``properties`` is deprecated and will stop working in the next major version.", + "isOptional": false, + "type": "BaseException" + }, + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalCaptureArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Capture exception", + "code": "# Capture exception\nfrom posthog import capture_exception\ntry:\n risky_operation()\nexcept Exception as e:\n capture_exception(e)" + } + ] + }, + { + "id": "evaluate_flags", + "title": "evaluate_flags", + "description": "Evaluate all feature flags for a user in a single call and return a :class:`FeatureFlagEvaluations` snapshot. Branch on ``.is_enabled()`` / ``.get_flag()`` and pass the same snapshot to ``capture()`` via the ``flags`` option so events carry the exact flag values the code branched on. Prefer this over repeated ``get_feature_flag()`` calls and over ``capture(send_feature_flags=True)`` \u2014 it consolidates flag evaluation into a single ``/flags`` request per incoming request.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The user's distinct ID. If ``None``, falls back to the context distinct_id. If still unresolvable, returns an empty snapshot.", + "isOptional": false, + "type": "Number" + }, + { + "name": "groups", + "description": "Mapping of group type to group key.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "Person properties to use for evaluation.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "Group properties keyed by group type.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "If ``True``, never fall back to remote evaluation and omit flags that cannot be evaluated locally. The snapshot's ``unresolved_flags`` lists them with a reason.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup.", + "isOptional": true, + "type": "bool" + }, + { + "name": "flag_keys", + "description": "Optional list that scopes local evaluation, the underlying ``/flags`` request, and the returned snapshot. When omitted or ``None``, all flags are evaluated. An empty list returns an empty snapshot without evaluating flags. A requested key absent from loaded local definitions is included in one remote fallback per ``evaluate_flags`` call unless ``only_evaluate_locally`` is ``True``. If the server also does not know the key, it is omitted from the snapshot.", + "isOptional": true, + "type": "list[str]" + }, + { + "name": "device_id", + "description": "Optional device ID override. If not provided, falls back to the context device_id (which may be set via tracing headers). Used by experience-continuity flags to match users across distinct_id changes.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "FeatureFlagEvaluations" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import evaluate_flags, capture\nflags = evaluate_flags(\"user_123\", person_properties={\"plan\": \"enterprise\"})\nif flags.is_enabled(\"new-dashboard\"):\n render_new_dashboard()\ncapture(\"page_viewed\", distinct_id=\"user_123\", flags=flags)" + } + ] + }, + { + "id": "feature_enabled", + "title": "feature_enabled", + "description": "Use feature flags to enable or disable features for users.", + "details": "You can call `posthog.load_feature_flags()` before to make sure you're not doing unexpected requests.", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "The feature flag key", + "isOptional": true, + "type": "str" + }, + { + "name": "distinct_id", + "description": "The user's distinct ID", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "Groups mapping", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "Person properties", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "Group properties", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to evaluate only locally", + "isOptional": false, + "type": "bool" + }, + { + "name": "send_feature_flag_events", + "description": "Whether to send feature flag events", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "Optional device ID override for experience-continuity flags", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[bool]" + }, + "examples": [ + { + "id": "example_1", + "name": "Boolean feature flag", + "code": "# Boolean feature flag\nfrom posthog import feature_enabled, get_feature_flag_payload\nis_my_flag_enabled = feature_enabled('flag-key', 'distinct_id_of_your_user')\nif is_my_flag_enabled:\n matched_flag_payload = get_feature_flag_payload('flag-key', 'distinct_id_of_your_user')" + } + ] + }, + { + "id": "feature_flag_definitions", + "title": "feature_flag_definitions", + "description": "Returns loaded feature flags.", + "details": "Returns loaded feature flags, if any. Helpful for debugging what flag information you have loaded.", + "category": "Feature flags", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import feature_flag_definitions\ndefinitions = feature_flag_definitions()" + } + ] + }, + { + "id": "flush", + "title": "flush", + "description": "Attempt immediate delivery of queued events and ended spans, keeping the global client reusable. Call ``flush()`` before a short-lived request or serverless invocation returns when using the shared module-level client. Use ``shutdown()`` only for final process cleanup or a client that will not be reused. Flush does not stop the background workers or feature flag poller, and does not end open spans. Returning does not guarantee server receipt: pending work can remain after a timeout or span export failure, and failed events may be dropped by consumers. Calls made directly from SDK callbacks such as ``on_error`` are deferred rather than blocking the worker that invoked the callback.", + "details": "", + "category": "Client management", + "params": [ + { + "name": "timeout_seconds", + "description": "Seconds to wait for event queues to drain, shared across event lanes. Defaults to 10 seconds. Pass ``None`` to wait without a time limit; this does not guarantee successful delivery. Queued spans flush concurrently, waiting up to this many seconds for an in-flight flush before starting a fresh drain budget. A lock wait timeout leaves the in-flight flush to finish. At least one span request is attempted after acquiring the lock, even with no budget left; retriable failures are retried after backoff while budget remains, with one last attempt at the deadline. The last span request is not cut short and is bounded by the client's ``timeout`` in seconds, so this is not a strict wall-clock limit for the whole call. With ``None``, a retriable span failure is left for a later flush rather than retried within this call.", + "isOptional": true, + "type": "float" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "import posthog\nposthog.capture('event_name', distinct_id='user_id')\n# Before a serverless invocation returns; keep the global client open.\nposthog.flush(timeout_seconds=10)" + } + ] + }, + { + "id": "get_active_span", + "title": "get_active_span", + "description": "The span that is active in the current context, or ``None``. Alpha. Only entering a span (``with posthog.start_span(...) as span:``) makes it active; a span started manually is not. Use it to propagate the trace to the next service: ``span.traceparent()`` is the header value.", + "details": "", + "category": "Tracing", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[Span]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "span = posthog.get_active_span()\nif span is not None:\n headers[\"traceparent\"] = span.traceparent()" + } + ] + }, + { + "id": "get_all_flags", + "title": "get_all_flags", + "description": "Get all flags for a given user.", + "details": "Flags are key-value pairs where the key is the flag key and the value is the flag variant, or True, or False.", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The user's distinct ID", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "Groups mapping", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "Person properties", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "Group properties", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to evaluate only locally", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "Optional device ID override for experience-continuity flags", + "isOptional": true, + "type": "str" + }, + { + "name": "flag_keys_to_evaluate", + "description": "Optional list of flag keys to evaluate (evaluates all if None)", + "isOptional": true, + "type": "list[str]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[dict[str, Union[bool, str]]]" + }, + "examples": [ + { + "id": "example_1", + "name": "All flags for user", + "code": "# All flags for user\nfrom posthog import get_all_flags\nget_all_flags('distinct_id_of_your_user')" + } + ] + }, + { + "id": "get_all_flags_and_payloads", + "title": "get_all_flags_and_payloads", + "description": "Get all feature flag values and payloads for a user.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "distinct_id", + "description": "The user's distinct ID.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "Mapping of group type to group key.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "Person properties to use for evaluation.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "Group properties keyed by group type.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to evaluate only locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup.", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "Optional device ID override for experience-continuity flags.", + "isOptional": true, + "type": "str" + }, + { + "name": "flag_keys_to_evaluate", + "description": "Optional list of flag keys to evaluate. Evaluates all flags when omitted.", + "isOptional": true, + "type": "list[str]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "FlagsAndPayloads" + } + }, + { + "id": "get_feature_flag", + "title": "get_feature_flag", + "description": "Get feature flag variant for users. Used with experiments.", + "details": "`groups` are a mapping from group type to group key. So, if you have a group type of \"organization\" and a group key of \"5\", you would pass groups={\"organization\": \"5\"}. `group_properties` take the format: { group_type_name: { group_properties } }. So, for example, if you have the group type \"organization\" and the group key \"5\", with the properties name, and employee count, you'll send these as: group_properties={\"organization\": {\"name\": \"PostHog\", \"employees\": 11}}.", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "The feature flag key", + "isOptional": true, + "type": "str" + }, + { + "name": "distinct_id", + "description": "The user's distinct ID", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "Groups mapping from group type to group key", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "Person properties", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "Group properties in format { group_type_name: { group_properties } }", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to evaluate only locally", + "isOptional": false, + "type": "bool" + }, + { + "name": "send_feature_flag_events", + "description": "Whether to send feature flag events", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "Optional device ID override for experience-continuity flags", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Union[bool, str, any]" + }, + "examples": [ + { + "id": "example_1", + "name": "Multivariate feature flag", + "code": "# Multivariate feature flag\nfrom posthog import get_feature_flag, get_feature_flag_payload\nenabled_variant = get_feature_flag('flag-key', 'distinct_id_of_your_user')\nif enabled_variant == 'variant-key':\n matched_flag_payload = get_feature_flag_payload('flag-key', 'distinct_id_of_your_user')" + } + ] + }, + { + "id": "get_feature_flag_evaluation_runtime", + "title": "get_feature_flag_evaluation_runtime", + "description": "Return where a locally loaded feature flag is meant to be evaluated.", + "details": "Reads the `evaluation_runtime` each flag definition carries, so no extra request is made. Returns `None` when local evaluation has not loaded a definition for this key. A definition that carries no runtime reports `FeatureFlagEvaluationRuntime.ALL`, the default PostHog applies.", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[FeatureFlagEvaluationRuntime]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import FeatureFlagEvaluationRuntime, get_feature_flag_evaluation_runtime\nruntime = get_feature_flag_evaluation_runtime(\"my-flag\")" + } + ] + }, + { + "id": "get_feature_flag_keys_by_evaluation_runtime", + "title": "get_feature_flag_keys_by_evaluation_runtime", + "description": "Return the keys of locally loaded flags that a runtime can evaluate.", + "details": "A flag set to `FeatureFlagEvaluationRuntime.ALL` suits either runtime, so it is returned for `CLIENT` and for `SERVER`. Use this to decide which flags to hand to a browser when a backend serves flags to its own frontend.", + "category": "Feature flags", + "params": [ + { + "name": "evaluation_runtime", + "description": "", + "isOptional": true, + "type": "FeatureFlagEvaluationRuntime" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "list[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import FeatureFlagEvaluationRuntime, get_feature_flag_keys_by_evaluation_runtime\nclient_keys = get_feature_flag_keys_by_evaluation_runtime(FeatureFlagEvaluationRuntime.CLIENT)" + } + ] + }, + { + "id": "get_feature_flag_payload", + "title": "get_feature_flag_payload", + "description": "Get the payload associated with a feature flag value. Deprecated for new code. Prefer ``evaluate_flags()`` and ``flags.get_flag_payload(key)`` so flag evaluation happens once per request.", + "details": "", + "category": "Feature flags", + "params": [ + { + "name": "key", + "description": "The feature flag key.", + "isOptional": true, + "type": "str" + }, + { + "name": "distinct_id", + "description": "The user's distinct ID.", + "isOptional": true, + "type": "Number" + }, + { + "name": "match_value", + "description": "Optional flag value to use when selecting a payload.", + "isOptional": false, + "type": "bool" + }, + { + "name": "groups", + "description": "Mapping of group type to group key.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "Person properties to use for evaluation.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "Group properties keyed by group type.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to evaluate only locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "send_feature_flag_events", + "description": "Whether to send a $feature_flag_called event.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup.", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "Optional device ID override for experience-continuity flags.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[object]" + } + }, + { + "id": "get_feature_flag_result", + "title": "get_feature_flag_result", + "description": "Get a FeatureFlagResult object which contains the flag result and payload. This method evaluates a feature flag and returns a FeatureFlagResult object containing: - enabled: Whether the flag is enabled - variant: The variant value if the flag has variants - payload: The payload associated with the flag (automatically deserialized from JSON) - key: The flag key - reason: Why the flag was enabled/disabled", + "details": "", + "category": null, + "params": [ + { + "name": "key", + "description": "The feature flag key.", + "isOptional": true, + "type": "str" + }, + { + "name": "distinct_id", + "description": "The user's distinct ID.", + "isOptional": true, + "type": "Number" + }, + { + "name": "groups", + "description": "Mapping of group type to group key.", + "isOptional": true, + "type": "Mapping[str, Union[str, int]]" + }, + { + "name": "person_properties", + "description": "Person properties to use for evaluation.", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "group_properties", + "description": "Group properties keyed by group type.", + "isOptional": true, + "type": "dict[str, dict[str, Any]]" + }, + { + "name": "only_evaluate_locally", + "description": "Whether to evaluate only locally.", + "isOptional": false, + "type": "bool" + }, + { + "name": "send_feature_flag_events", + "description": "Whether to send a $feature_flag_called event.", + "isOptional": false, + "type": "bool" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup.", + "isOptional": true, + "type": "bool" + }, + { + "name": "device_id", + "description": "Optional device ID override for experience-continuity flags.", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[FeatureFlagResult]" + } + }, + { + "id": "get_remote_config_payload", + "title": "get_remote_config_payload", + "description": "Get the payload for a remote config feature flag.", + "details": "", + "category": null, + "params": [ + { + "name": "key", + "description": "The key of the feature flag", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "get_tags", + "title": "get_tags", + "description": "Get all tags from the current context. Returns: Dict of all tags in the current context", + "details": "", + "category": "Contexts", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "dict[str, Any]" + } + }, + { + "id": "group_identify", + "title": "group_identify", + "description": "Set properties on a group.", + "details": "", + "category": "Identification", + "params": [ + { + "name": "group_type", + "description": "Type of your group. Required - the call is dropped with a warning if it is missing or empty.", + "isOptional": true, + "type": "str" + }, + { + "name": "group_key", + "description": "Unique identifier of the group. Required - the call is dropped with a warning if it is missing or empty.", + "isOptional": true, + "type": "str" + }, + { + "name": "properties", + "description": "Properties to set on the group", + "isOptional": true, + "type": "dict[str, Any]" + }, + { + "name": "timestamp", + "description": "Optional timestamp for the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC.", + "isOptional": false, + "type": "datetime" + }, + { + "name": "uuid", + "description": "Optional UUID for the event", + "isOptional": true, + "type": "str" + }, + { + "name": "disable_geoip", + "description": "Whether to disable GeoIP lookup", + "isOptional": true, + "type": "bool" + }, + { + "name": "distinct_id", + "description": "Optional distinct ID of the user performing the action", + "isOptional": false, + "type": "Number" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Group identify", + "code": "# Group identify\nfrom posthog import group_identify\ngroup_identify('company', 'company_id_in_your_db', {\n 'name': 'Awesome Inc.',\n 'employees': 11\n})" + } + ] + }, + { + "id": "identify_context", + "title": "identify_context", + "description": "Identify the current context with a distinct ID.", + "details": "", + "category": "Identification", + "params": [ + { + "name": "distinct_id", + "description": "The distinct ID to associate with the current context and its children", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import identify_context\nidentify_context(\"user_123\")" + } + ] + }, + { + "id": "join", + "title": "join", + "description": "Attempt to process queued events and stop the client's background workers. Use `shutdown()` directly in most cases. Failed or undrainable events may be dropped and reported through logging or ``on_error``; returning does not guarantee server receipt. Lifecycle cleanup is attempted once, and cleanup failures are logged without retry.", + "details": "", + "category": "Client management", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import join\njoin()" + } + ] + }, + { + "id": "load_feature_flags", + "title": "load_feature_flags", + "description": "Load feature flag definitions from PostHog.", + "details": "", + "category": "Feature flags", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import load_feature_flags\nload_feature_flags()" + } + ] + }, + { + "id": "new_context", + "title": "new_context", + "description": "Create a new context scope that will be active for the duration of the with block.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "fresh", + "description": "Whether to start with a fresh context (default: False)", + "isOptional": false, + "type": "bool" + }, + { + "name": "capture_exceptions", + "description": "Whether to capture exceptions raised within the context. If omitted, defaults to the relevant client's exception autocapture setting.", + "isOptional": true, + "type": "bool" + }, + { + "name": "client", + "description": "Optional Posthog client instance to use for this context (default: None)", + "isOptional": true, + "type": "Client" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import new_context, tag, capture\nwith new_context():\n tag(\"request_id\", \"123\")\n capture(\"event_name\", properties={\"property\": \"value\"})" + } + ] + }, + { + "id": "scoped", + "title": "scoped", + "description": "Decorator that creates a new context for the function.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "fresh", + "description": "Whether to start with a fresh context (default: False)", + "isOptional": false, + "type": "bool" + }, + { + "name": "capture_exceptions", + "description": "Whether to capture and track exceptions with posthog error tracking. If omitted, defaults to the global exception autocapture setting.", + "isOptional": true, + "type": "bool" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import scoped, tag, capture\n@scoped()\ndef process_payment(payment_id):\n tag(\"payment_id\", payment_id)\n capture(\"payment_started\")" + } + ] + }, + { + "id": "set", + "title": "set", + "description": "Set properties on a user record.", + "details": "This will overwrite previous people property values. Generally operates similar to `capture`, with distinct_id being an optional argument, defaulting to the current context's distinct ID. If there is no context-level distinct ID, and no override distinct_id is passed, this function will do nothing. Context tags are folded into $set properties, so tagging the current context and then calling `set` will cause those tags to be set on the user (unlike capture, which causes them to just be set on the event).", + "category": "Identification", + "params": [ + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalSetArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Set person properties", + "code": "# Set person properties\nfrom posthog import set\nset(distinct_id='distinct_id', properties={'name': 'Max Hedgehog'})" + } + ] + }, + { + "id": "set_capture_exception_code_variables_context", + "title": "set_capture_exception_code_variables_context", + "description": "Override code-variable capture for exceptions in the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "enabled", + "description": "Whether exceptions captured in this context should include local variable values from stack frames.", + "isOptional": true, + "type": "bool" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "set_code_variables_detect_secrets_context", + "title": "set_code_variables_detect_secrets_context", + "description": "Whether to apply entropy-based secret detection as a last-resort redaction of high-entropy values (API keys, tokens, strong passwords) in captured code variables for the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "enabled", + "description": "", + "isOptional": true, + "type": "bool" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "set_code_variables_ignore_patterns_context", + "title": "set_code_variables_ignore_patterns_context", + "description": "Override code-variable ignore patterns for exceptions in the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "ignore_patterns", + "description": "Variable-name patterns that should be omitted entirely when code variables are captured.", + "isOptional": true, + "type": "list[str]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "set_code_variables_mask_patterns_context", + "title": "set_code_variables_mask_patterns_context", + "description": "Override code-variable mask patterns for exceptions in the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "mask_patterns", + "description": "Variable-name patterns whose values should be replaced with ``***`` when code variables are captured.", + "isOptional": true, + "type": "list[str]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "set_code_variables_mask_url_credentials_context", + "title": "set_code_variables_mask_url_credentials_context", + "description": "Whether to scrub credentials embedded in URLs/DSNs (e.g. user:pass@host) from captured code variables for the current context.", + "details": "", + "category": null, + "params": [ + { + "name": "enabled", + "description": "", + "isOptional": true, + "type": "bool" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + } + }, + { + "id": "set_context_device_id", + "title": "set_context_device_id", + "description": "Set the device ID for the current context, associating all feature flag requests in this or child contexts with the given device ID.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "device_id", + "description": "The device ID to associate with the current context and its children", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import set_context_device_id\nset_context_device_id(\"device_123\")" + } + ] + }, + { + "id": "set_context_session", + "title": "set_context_session", + "description": "Set the session ID for the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "session_id", + "description": "The session ID to associate with the current context and its children", + "isOptional": true, + "type": "str" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import set_context_session\nset_context_session(\"session_123\")" + } + ] + }, + { + "id": "set_once", + "title": "set_once", + "description": "Set properties on a user record, only if they do not yet exist.", + "details": "This will not overwrite previous people property values, unlike `set`. Otherwise, operates in an identical manner to `set`.", + "category": "Identification", + "params": [ + { + "name": "kwargs", + "description": "", + "isOptional": true, + "type": "Unpack[OptionalSetArgs]" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Optional[str]" + }, + "examples": [ + { + "id": "example_1", + "name": "Set property once", + "code": "# Set property once\nfrom posthog import set_once\nset_once(distinct_id='distinct_id', properties={'initial_url': '/blog'})" + } + ] + }, + { + "id": "setup", + "title": "setup", + "description": "Create or return the global PostHog client configured by module settings. Most applications should either instantiate ``Posthog`` directly or set ``posthog.api_key``/other module settings before calling top-level helpers. ``setup()`` is called automatically by global APIs such as ``capture()``. Returns: The global ``Client`` instance. If both ``api_key`` and ``project_api_key`` are missing or blank, the client is disabled and module-level calls become no-ops.", + "details": "", + "category": "Initialization", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Client" + } + }, + { + "id": "shutdown", + "title": "shutdown", + "description": "Attempt final delivery and permanently shut down the global client. Call this for final process cleanup, not at the end of each request or serverless invocation that shares the module-level client. Use ``flush()`` before returning from those invocations instead. Shutdown closes capture lanes, stops background workers and the feature flag poller, and tears down integrations. The global client is not recreated automatically after shutdown. This normally blocks until queued events have been attempted and cleanup finishes. Failed or undrainable events may be dropped and reported through logging or ``on_error``; returning does not guarantee server receipt. Event draining has no timeout. Queued spans get a final flush with up to 30 seconds waiting for an in-flight flush, then a fresh 30-second drain budget; the last HTTP request is not cut short. Unsent queued spans and still-open spans are discarded with a warning. Lifecycle cleanup is attempted once, and cleanup failures are logged without retry. Calls made directly from SDK callbacks such as ``on_error`` are deferred to avoid deadlocking the worker. If blocking completion is required, signal an application-owned thread, return from the callback, and call ``shutdown()`` from that thread. Do not wait inside a callback for another thread or task calling a lifecycle method.", + "details": "", + "category": "Client management", + "params": [], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "any" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import shutdown\nshutdown()" + } + ] + }, + { + "id": "start_span", + "title": "start_span", + "description": "Start a span for distributed tracing. Alpha. Returns a span handle. Use it as a context manager to make it the active span for the block and end it on exit (recording a raised exception on the way out); or call ``end()`` yourself for a span that cannot wrap a block. Spans started inside the block nest under it automatically. Always returns a usable handle, even when tracing is off, so calling code never branches.", + "details": "", + "category": "Tracing", + "params": [ + { + "name": "name", + "description": "A low-cardinality operation name, e.g. ``GET /users/:id``. Variable values belong in attributes, not the name.", + "isOptional": true, + "type": "str" + }, + { + "name": "kind", + "description": "``internal`` (default), ``server``, ``client``, ``producer`` or ``consumer``.", + "isOptional": true, + "type": "str" + }, + { + "name": "attributes", + "description": "Initial attributes.", + "isOptional": true, + "type": "Mapping[str, Any]" + }, + { + "name": "parent", + "description": "A span handle, or an inbound W3C ``traceparent`` header value to continue a remote trace. Defaults to the active span.", + "isOptional": false, + "type": "Span" + }, + { + "name": "tracestate", + "description": "The inbound ``tracestate`` header accompanying a ``traceparent`` string ``parent``; preserved and propagated.", + "isOptional": true, + "type": "str" + }, + { + "name": "start_time", + "description": "A ``datetime`` or epoch seconds, to backdate the span.", + "isOptional": false, + "type": "datetime" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "Span" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "import posthog\nposthog.traces = {\"service_name\": \"checkout-api\"}\n\nwith posthog.start_span(\"POST /checkout\", parent=request.headers.get(\"traceparent\")) as span:\n span.set_attribute(\"plan\", user.plan)\n with posthog.start_span(\"db.query\", kind=\"client\"):\n ...\n outgoing_headers = {\"traceparent\": span.traceparent()}" + } + ] + }, + { + "id": "tag", + "title": "tag", + "description": "Add a tag to the current context.", + "details": "", + "category": "Contexts", + "params": [ + { + "name": "name", + "description": "The tag key", + "isOptional": true, + "type": "str" + }, + { + "name": "value", + "description": "The tag value", + "isOptional": true, + "type": "Any" + } + ], + "showDocs": true, + "releaseTag": "public", + "returnType": { + "id": "return_type", + "name": "None" + }, + "examples": [ + { + "id": "example_1", + "name": "Example 1", + "code": "from posthog import tag\ntag(\"user_id\", \"123\")" + } + ] + } + ] + } + ], + "categories": [ + "Initialization", + "Identification", + "Capture", + "Error Tracking", + "Feature flags", + "Tracing", + "Contexts", + "Events", + "Client management" + ] +} \ No newline at end of file diff --git a/references/posthog-python-references-latest.json b/references/posthog-python-references-latest.json index d8cd4506..8ee9a06c 100644 --- a/references/posthog-python-references-latest.json +++ b/references/posthog-python-references-latest.json @@ -2,7 +2,7 @@ "id": "posthog-python", "hogRef": "0.3", "info": { - "version": "7.80.2", + "version": "7.80.3", "id": "posthog-python", "title": "PostHog Python SDK", "description": "Integrate PostHog into any python application.", From f144c9fbc1c7513475c4608835ddfa4a4c39153e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 29 Oct 2026 16:58:37 -0400 Subject: [PATCH 50/60] chore(deps): bump setuptools from 83.0.0 to 84.1.0 in /examples/example-ai-autogen in the uv group across 1 directory (#1131) chore(deps): bump setuptools Bumps the uv group with 1 update in the /examples/example-ai-llamaindex directory: [setuptools](https://github.com/pypa/setuptools). Updates `setuptools` from 83.0.0 to 84.1.0 - [Release notes](https://github.com/pypa/setuptools/releases) - [Changelog](https://github.com/pypa/setuptools/blob/main/NEWS.rst) - [Commits](https://github.com/pypa/setuptools/compare/v83.0.0...v84.1.0) --- updated-dependencies: - dependency-name: setuptools dependency-version: 84.1.0 dependency-type: indirect dependency-group: uv ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> (cherry picked from commit 6f765a3cb491de5b414e157e095c8155b59f18e2) --- examples/example-ai-autogen/uv.lock | 8 ++++---- examples/example-ai-crewai/uv.lock | 6 +++--- examples/example-ai-dspy/uv.lock | 6 +++--- examples/example-ai-litellm/uv.lock | 6 +++--- examples/example-ai-llamaindex/uv.lock | 14 +++++++------- examples/example-ai-openai-agents/uv.lock | 6 +++--- examples/example-ai-pydantic-ai/uv.lock | 8 ++++---- examples/example-ai-smolagents/uv.lock | 8 ++++---- integration_tests/django5/uv.lock | 8 ++++---- 9 files changed, 35 insertions(+), 35 deletions(-) diff --git a/examples/example-ai-autogen/uv.lock b/examples/example-ai-autogen/uv.lock index b1cbe8a8..828f4ba2 100644 --- a/examples/example-ai-autogen/uv.lock +++ b/examples/example-ai-autogen/uv.lock @@ -100,11 +100,11 @@ wheels = [ [[package]] name = "certifi" -version = "2026.10.19" +version = "2026.10.23" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a6/bd/1436c50cc46699de0230fbaacc1d1de7bfec413176099949e9d5112e88f2/certifi-2026.10.19.tar.gz", hash = "sha256:035c1e85572b141bf1c3a3eff0e33a8eb74917522f2559075ff8d2fea780a1ae", size = 137598, upload-time = "2026-10-20T02:24:16.604Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fd/11/b524cbd9bbdaca58608e4cf4053fd463417549e1959fa8d3aa18319f9555/certifi-2026.10.23.tar.gz", hash = "sha256:a9895e6e2758b371e0545e321a3a8fd1fcfd9829ada8824a936dfb16aaaf36cd", size = 136225, upload-time = "2026-10-23T11:36:16.478Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c4/51/29046e48f693c54e5b7412216d447a1d8362c9653148d98c02cdf65fa733/certifi-2026.10.19-py3-none-any.whl", hash = "sha256:42d35340cc5b9ba78e818ddbff0c5b1c2b779de448a340fd1b989f5fa8670e1b", size = 136464, upload-time = "2026-10-20T02:24:15.072Z" }, + { url = "https://files.pythonhosted.org/packages/32/d1/a3652fe2c19134d0d15922177821d863cbd7e842d53a21bbe7e8e3a1b5ac/certifi-2026.10.23-py3-none-any.whl", hash = "sha256:e9b54de76e530bb6cf0ccd036787103acb58fd42ba930ea8bf948ddbaa2f61d1", size = 135082, upload-time = "2026-10-23T11:36:14.763Z" }, ] [[package]] @@ -694,7 +694,7 @@ wheels = [ [[package]] name = "posthog" -version = "7.75.11" +version = "7.76.0" source = { directory = "../../" } dependencies = [ { name = "backoff" }, diff --git a/examples/example-ai-crewai/uv.lock b/examples/example-ai-crewai/uv.lock index 2d731ef0..c12480df 100644 --- a/examples/example-ai-crewai/uv.lock +++ b/examples/example-ai-crewai/uv.lock @@ -403,11 +403,11 @@ wheels = [ [[package]] name = "certifi" -version = "2026.10.19" +version = "2026.10.23" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a6/bd/1436c50cc46699de0230fbaacc1d1de7bfec413176099949e9d5112e88f2/certifi-2026.10.19.tar.gz", hash = "sha256:035c1e85572b141bf1c3a3eff0e33a8eb74917522f2559075ff8d2fea780a1ae", size = 137598, upload-time = "2026-10-20T02:24:16.604Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fd/11/b524cbd9bbdaca58608e4cf4053fd463417549e1959fa8d3aa18319f9555/certifi-2026.10.23.tar.gz", hash = "sha256:a9895e6e2758b371e0545e321a3a8fd1fcfd9829ada8824a936dfb16aaaf36cd", size = 136225, upload-time = "2026-10-23T11:36:16.478Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c4/51/29046e48f693c54e5b7412216d447a1d8362c9653148d98c02cdf65fa733/certifi-2026.10.19-py3-none-any.whl", hash = "sha256:42d35340cc5b9ba78e818ddbff0c5b1c2b779de448a340fd1b989f5fa8670e1b", size = 136464, upload-time = "2026-10-20T02:24:15.072Z" }, + { url = "https://files.pythonhosted.org/packages/32/d1/a3652fe2c19134d0d15922177821d863cbd7e842d53a21bbe7e8e3a1b5ac/certifi-2026.10.23-py3-none-any.whl", hash = "sha256:e9b54de76e530bb6cf0ccd036787103acb58fd42ba930ea8bf948ddbaa2f61d1", size = 135082, upload-time = "2026-10-23T11:36:14.763Z" }, ] [[package]] diff --git a/examples/example-ai-dspy/uv.lock b/examples/example-ai-dspy/uv.lock index c58b3670..60c92785 100644 --- a/examples/example-ai-dspy/uv.lock +++ b/examples/example-ai-dspy/uv.lock @@ -302,11 +302,11 @@ wheels = [ [[package]] name = "certifi" -version = "2026.10.19" +version = "2026.10.23" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a6/bd/1436c50cc46699de0230fbaacc1d1de7bfec413176099949e9d5112e88f2/certifi-2026.10.19.tar.gz", hash = "sha256:035c1e85572b141bf1c3a3eff0e33a8eb74917522f2559075ff8d2fea780a1ae", size = 137598, upload-time = "2026-10-20T02:24:16.604Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fd/11/b524cbd9bbdaca58608e4cf4053fd463417549e1959fa8d3aa18319f9555/certifi-2026.10.23.tar.gz", hash = "sha256:a9895e6e2758b371e0545e321a3a8fd1fcfd9829ada8824a936dfb16aaaf36cd", size = 136225, upload-time = "2026-10-23T11:36:16.478Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c4/51/29046e48f693c54e5b7412216d447a1d8362c9653148d98c02cdf65fa733/certifi-2026.10.19-py3-none-any.whl", hash = "sha256:42d35340cc5b9ba78e818ddbff0c5b1c2b779de448a340fd1b989f5fa8670e1b", size = 136464, upload-time = "2026-10-20T02:24:15.072Z" }, + { url = "https://files.pythonhosted.org/packages/32/d1/a3652fe2c19134d0d15922177821d863cbd7e842d53a21bbe7e8e3a1b5ac/certifi-2026.10.23-py3-none-any.whl", hash = "sha256:e9b54de76e530bb6cf0ccd036787103acb58fd42ba930ea8bf948ddbaa2f61d1", size = 135082, upload-time = "2026-10-23T11:36:14.763Z" }, ] [[package]] diff --git a/examples/example-ai-litellm/uv.lock b/examples/example-ai-litellm/uv.lock index d48afee2..43da30da 100644 --- a/examples/example-ai-litellm/uv.lock +++ b/examples/example-ai-litellm/uv.lock @@ -262,11 +262,11 @@ wheels = [ [[package]] name = "certifi" -version = "2026.10.19" +version = "2026.10.23" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a6/bd/1436c50cc46699de0230fbaacc1d1de7bfec413176099949e9d5112e88f2/certifi-2026.10.19.tar.gz", hash = "sha256:035c1e85572b141bf1c3a3eff0e33a8eb74917522f2559075ff8d2fea780a1ae", size = 137598, upload-time = "2026-10-20T02:24:16.604Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fd/11/b524cbd9bbdaca58608e4cf4053fd463417549e1959fa8d3aa18319f9555/certifi-2026.10.23.tar.gz", hash = "sha256:a9895e6e2758b371e0545e321a3a8fd1fcfd9829ada8824a936dfb16aaaf36cd", size = 136225, upload-time = "2026-10-23T11:36:16.478Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c4/51/29046e48f693c54e5b7412216d447a1d8362c9653148d98c02cdf65fa733/certifi-2026.10.19-py3-none-any.whl", hash = "sha256:42d35340cc5b9ba78e818ddbff0c5b1c2b779de448a340fd1b989f5fa8670e1b", size = 136464, upload-time = "2026-10-20T02:24:15.072Z" }, + { url = "https://files.pythonhosted.org/packages/32/d1/a3652fe2c19134d0d15922177821d863cbd7e842d53a21bbe7e8e3a1b5ac/certifi-2026.10.23-py3-none-any.whl", hash = "sha256:e9b54de76e530bb6cf0ccd036787103acb58fd42ba930ea8bf948ddbaa2f61d1", size = 135082, upload-time = "2026-10-23T11:36:14.763Z" }, ] [[package]] diff --git a/examples/example-ai-llamaindex/uv.lock b/examples/example-ai-llamaindex/uv.lock index af7f08f0..25decdb5 100644 --- a/examples/example-ai-llamaindex/uv.lock +++ b/examples/example-ai-llamaindex/uv.lock @@ -293,11 +293,11 @@ wheels = [ [[package]] name = "certifi" -version = "2026.10.19" +version = "2026.10.23" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a6/bd/1436c50cc46699de0230fbaacc1d1de7bfec413176099949e9d5112e88f2/certifi-2026.10.19.tar.gz", hash = "sha256:035c1e85572b141bf1c3a3eff0e33a8eb74917522f2559075ff8d2fea780a1ae", size = 137598, upload-time = "2026-10-20T02:24:16.604Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fd/11/b524cbd9bbdaca58608e4cf4053fd463417549e1959fa8d3aa18319f9555/certifi-2026.10.23.tar.gz", hash = "sha256:a9895e6e2758b371e0545e321a3a8fd1fcfd9829ada8824a936dfb16aaaf36cd", size = 136225, upload-time = "2026-10-23T11:36:16.478Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c4/51/29046e48f693c54e5b7412216d447a1d8362c9653148d98c02cdf65fa733/certifi-2026.10.19-py3-none-any.whl", hash = "sha256:42d35340cc5b9ba78e818ddbff0c5b1c2b779de448a340fd1b989f5fa8670e1b", size = 136464, upload-time = "2026-10-20T02:24:15.072Z" }, + { url = "https://files.pythonhosted.org/packages/32/d1/a3652fe2c19134d0d15922177821d863cbd7e842d53a21bbe7e8e3a1b5ac/certifi-2026.10.23-py3-none-any.whl", hash = "sha256:e9b54de76e530bb6cf0ccd036787103acb58fd42ba930ea8bf948ddbaa2f61d1", size = 135082, upload-time = "2026-10-23T11:36:14.763Z" }, ] [[package]] @@ -1826,7 +1826,7 @@ wheels = [ [[package]] name = "posthog" -version = "7.75.11" +version = "7.76.0" source = { directory = "../../" } dependencies = [ { name = "backoff" }, @@ -2373,11 +2373,11 @@ wheels = [ [[package]] name = "setuptools" -version = "83.0.0" +version = "84.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/34/26/f5d29e25ffdb535afef2d35cdb55b325298f96debd670da4c325e08d70f4/setuptools-83.0.0.tar.gz", hash = "sha256:025bccbbf0fa05b6192bc64ae1e7b16e001fd6d6d4d5de03c97b1c1ade523bef", size = 1154254, upload-time = "2026-07-04T15:31:22.699Z" } +sdist = { url = "https://files.pythonhosted.org/packages/73/eb/7e5f628ee2eb168fecbf8026de7856b2543d6d609cc85f086b9a91ac93d6/setuptools-84.1.0.tar.gz", hash = "sha256:23657205b63d4d31395ab917815247f694aafad28cd21316bf5fd45a51048ea0", size = 1171783, upload-time = "2026-10-19T20:11:43.489Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5d/40/e1e72872c6354b306daef1703549e8e83b4d43cfea356311bf722a043752/setuptools-83.0.0-py3-none-any.whl", hash = "sha256:29b23c360f22f414dc7336bb39178cc7bcbf6021ed2733cde173f09dba19abb3", size = 1008090, upload-time = "2026-07-04T15:31:20.885Z" }, + { url = "https://files.pythonhosted.org/packages/18/02/b4bbb90f8f29e334898ff45c6d9b1fffaa6f378017052b3023a06262b5f7/setuptools-84.1.0-py3-none-any.whl", hash = "sha256:5231689c6ac8156cd155cfb98c3fd6fb252c3e7e9bd3d78b64d3c55f132624c9", size = 819380, upload-time = "2026-10-19T20:11:41.684Z" }, ] [[package]] diff --git a/examples/example-ai-openai-agents/uv.lock b/examples/example-ai-openai-agents/uv.lock index b86553ea..fcf3831a 100644 --- a/examples/example-ai-openai-agents/uv.lock +++ b/examples/example-ai-openai-agents/uv.lock @@ -49,11 +49,11 @@ wheels = [ [[package]] name = "certifi" -version = "2026.10.19" +version = "2026.10.23" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a6/bd/1436c50cc46699de0230fbaacc1d1de7bfec413176099949e9d5112e88f2/certifi-2026.10.19.tar.gz", hash = "sha256:035c1e85572b141bf1c3a3eff0e33a8eb74917522f2559075ff8d2fea780a1ae", size = 137598, upload-time = "2026-10-20T02:24:16.604Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fd/11/b524cbd9bbdaca58608e4cf4053fd463417549e1959fa8d3aa18319f9555/certifi-2026.10.23.tar.gz", hash = "sha256:a9895e6e2758b371e0545e321a3a8fd1fcfd9829ada8824a936dfb16aaaf36cd", size = 136225, upload-time = "2026-10-23T11:36:16.478Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c4/51/29046e48f693c54e5b7412216d447a1d8362c9653148d98c02cdf65fa733/certifi-2026.10.19-py3-none-any.whl", hash = "sha256:42d35340cc5b9ba78e818ddbff0c5b1c2b779de448a340fd1b989f5fa8670e1b", size = 136464, upload-time = "2026-10-20T02:24:15.072Z" }, + { url = "https://files.pythonhosted.org/packages/32/d1/a3652fe2c19134d0d15922177821d863cbd7e842d53a21bbe7e8e3a1b5ac/certifi-2026.10.23-py3-none-any.whl", hash = "sha256:e9b54de76e530bb6cf0ccd036787103acb58fd42ba930ea8bf948ddbaa2f61d1", size = 135082, upload-time = "2026-10-23T11:36:14.763Z" }, ] [[package]] diff --git a/examples/example-ai-pydantic-ai/uv.lock b/examples/example-ai-pydantic-ai/uv.lock index 6679c95f..6e0b9ef7 100644 --- a/examples/example-ai-pydantic-ai/uv.lock +++ b/examples/example-ai-pydantic-ai/uv.lock @@ -427,11 +427,11 @@ wheels = [ [[package]] name = "certifi" -version = "2026.10.19" +version = "2026.10.23" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a6/bd/1436c50cc46699de0230fbaacc1d1de7bfec413176099949e9d5112e88f2/certifi-2026.10.19.tar.gz", hash = "sha256:035c1e85572b141bf1c3a3eff0e33a8eb74917522f2559075ff8d2fea780a1ae", size = 137598, upload-time = "2026-10-20T02:24:16.604Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fd/11/b524cbd9bbdaca58608e4cf4053fd463417549e1959fa8d3aa18319f9555/certifi-2026.10.23.tar.gz", hash = "sha256:a9895e6e2758b371e0545e321a3a8fd1fcfd9829ada8824a936dfb16aaaf36cd", size = 136225, upload-time = "2026-10-23T11:36:16.478Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c4/51/29046e48f693c54e5b7412216d447a1d8362c9653148d98c02cdf65fa733/certifi-2026.10.19-py3-none-any.whl", hash = "sha256:42d35340cc5b9ba78e818ddbff0c5b1c2b779de448a340fd1b989f5fa8670e1b", size = 136464, upload-time = "2026-10-20T02:24:15.072Z" }, + { url = "https://files.pythonhosted.org/packages/32/d1/a3652fe2c19134d0d15922177821d863cbd7e842d53a21bbe7e8e3a1b5ac/certifi-2026.10.23-py3-none-any.whl", hash = "sha256:e9b54de76e530bb6cf0ccd036787103acb58fd42ba930ea8bf948ddbaa2f61d1", size = 135082, upload-time = "2026-10-23T11:36:14.763Z" }, ] [[package]] @@ -2111,7 +2111,7 @@ wheels = [ [[package]] name = "posthog" -version = "7.75.4" +version = "7.76.0" source = { directory = "../../" } dependencies = [ { name = "backoff" }, diff --git a/examples/example-ai-smolagents/uv.lock b/examples/example-ai-smolagents/uv.lock index a35eb750..fe665389 100644 --- a/examples/example-ai-smolagents/uv.lock +++ b/examples/example-ai-smolagents/uv.lock @@ -43,11 +43,11 @@ wheels = [ [[package]] name = "certifi" -version = "2026.10.19" +version = "2026.10.23" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a6/bd/1436c50cc46699de0230fbaacc1d1de7bfec413176099949e9d5112e88f2/certifi-2026.10.19.tar.gz", hash = "sha256:035c1e85572b141bf1c3a3eff0e33a8eb74917522f2559075ff8d2fea780a1ae", size = 137598, upload-time = "2026-10-20T02:24:16.604Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fd/11/b524cbd9bbdaca58608e4cf4053fd463417549e1959fa8d3aa18319f9555/certifi-2026.10.23.tar.gz", hash = "sha256:a9895e6e2758b371e0545e321a3a8fd1fcfd9829ada8824a936dfb16aaaf36cd", size = 136225, upload-time = "2026-10-23T11:36:16.478Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c4/51/29046e48f693c54e5b7412216d447a1d8362c9653148d98c02cdf65fa733/certifi-2026.10.19-py3-none-any.whl", hash = "sha256:42d35340cc5b9ba78e818ddbff0c5b1c2b779de448a340fd1b989f5fa8670e1b", size = 136464, upload-time = "2026-10-20T02:24:15.072Z" }, + { url = "https://files.pythonhosted.org/packages/32/d1/a3652fe2c19134d0d15922177821d863cbd7e842d53a21bbe7e8e3a1b5ac/certifi-2026.10.23-py3-none-any.whl", hash = "sha256:e9b54de76e530bb6cf0ccd036787103acb58fd42ba930ea8bf948ddbaa2f61d1", size = 135082, upload-time = "2026-10-23T11:36:14.763Z" }, ] [[package]] @@ -813,7 +813,7 @@ wheels = [ [[package]] name = "posthog" -version = "7.75.11" +version = "7.76.0" source = { directory = "../../" } dependencies = [ { name = "backoff" }, diff --git a/integration_tests/django5/uv.lock b/integration_tests/django5/uv.lock index 8f39eb07..0eea38c3 100644 --- a/integration_tests/django5/uv.lock +++ b/integration_tests/django5/uv.lock @@ -42,11 +42,11 @@ wheels = [ [[package]] name = "certifi" -version = "2026.10.19" +version = "2026.10.23" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a6/bd/1436c50cc46699de0230fbaacc1d1de7bfec413176099949e9d5112e88f2/certifi-2026.10.19.tar.gz", hash = "sha256:035c1e85572b141bf1c3a3eff0e33a8eb74917522f2559075ff8d2fea780a1ae", size = 137598, upload-time = "2026-10-20T02:24:16.604Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fd/11/b524cbd9bbdaca58608e4cf4053fd463417549e1959fa8d3aa18319f9555/certifi-2026.10.23.tar.gz", hash = "sha256:a9895e6e2758b371e0545e321a3a8fd1fcfd9829ada8824a936dfb16aaaf36cd", size = 136225, upload-time = "2026-10-23T11:36:16.478Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c4/51/29046e48f693c54e5b7412216d447a1d8362c9653148d98c02cdf65fa733/certifi-2026.10.19-py3-none-any.whl", hash = "sha256:42d35340cc5b9ba78e818ddbff0c5b1c2b779de448a340fd1b989f5fa8670e1b", size = 136464, upload-time = "2026-10-20T02:24:15.072Z" }, + { url = "https://files.pythonhosted.org/packages/32/d1/a3652fe2c19134d0d15922177821d863cbd7e842d53a21bbe7e8e3a1b5ac/certifi-2026.10.23-py3-none-any.whl", hash = "sha256:e9b54de76e530bb6cf0ccd036787103acb58fd42ba930ea8bf948ddbaa2f61d1", size = 135082, upload-time = "2026-10-23T11:36:14.763Z" }, ] [[package]] @@ -254,7 +254,7 @@ wheels = [ [[package]] name = "posthog" -version = "7.75.4" +version = "7.76.0" source = { editable = "../../" } dependencies = [ { name = "backoff" }, From 36d5b70f0aef237b7a3ce1bab1853576c2d17632 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 29 Oct 2026 16:58:48 -0400 Subject: [PATCH 51/60] chore(deps): bump authlib from 1.7.2 to 1.9.1 (#1140) Bumps [authlib](https://github.com/authlib/authlib) from 1.7.2 to 1.9.1. - [Release notes](https://github.com/authlib/authlib/releases) - [Commits](https://github.com/authlib/authlib/compare/v1.7.2...v1.9.1) --- updated-dependencies: - dependency-name: authlib dependency-version: 1.9.1 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> (cherry picked from commit 871ad9e526058b889efd88957f29d0d86ccce5d0) --- uv.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/uv.lock b/uv.lock index f97bdc13..60140fbd 100644 --- a/uv.lock +++ b/uv.lock @@ -374,15 +374,15 @@ wheels = [ [[package]] name = "authlib" -version = "1.7.2" +version = "1.9.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cryptography" }, { name = "joserfc" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/36/98/7d93f30d029643c0275dbc0bd6d5a6f670661ee6c9a94d93af7ab4887600/authlib-1.7.2.tar.gz", hash = "sha256:2cea25fefcd4e7173bdf1372c0afc265c8034b23a8cd5dcb6a9164b826c64231", size = 176511, upload-time = "2026-05-06T08:10:23.116Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c9/dc/087c08741a98d234c9b98beef8a28d3c309f814d14b6156422c5815bf6d6/authlib-1.9.1.tar.gz", hash = "sha256:5c7d9848f47cac340f060f76ae0bc09b59f5c8e89cc1bb032d4370a0c55b5974", size = 182700, upload-time = "2026-10-14T14:17:52.307Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fb/95/adcb68e20c34162e9135f370d6e31737719c2b6f94bc953fe7ed1f10fe21/authlib-1.7.2-py2.py3-none-any.whl", hash = "sha256:3e1faedc9d87e7d56a164eca3ccb6ace0d61b94abe83e92242f8dc8bba9b4a9f", size = 259548, upload-time = "2026-05-06T08:10:21.436Z" }, + { url = "https://files.pythonhosted.org/packages/39/44/a46089266de32a0d809023b6bf31b80a1f26a46954446a7ce695a565cbea/authlib-1.9.1-py2.py3-none-any.whl", hash = "sha256:8b9be8b1e5174dcbf5520e984f1a22dc2edae5b6e40a61d4cedbf86b97e9e69b", size = 266042, upload-time = "2026-10-14T14:17:50.695Z" }, ] [[package]] From cdd9bfe7af011b8019adfc09514198e28a69f501 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 29 Oct 2026 16:58:57 -0400 Subject: [PATCH 52/60] chore(deps): bump the ai-providers group across 1 directory with 7 updates (#1145) Bumps the ai-providers group with 7 updates in the / directory: | Package | From | To | | --- | --- | --- | | [langchain](https://github.com/langchain-ai/langchain) | `1.4.4` | `1.4.7` | | [langgraph](https://github.com/langchain-ai/langgraph) | `1.2.14` | `1.2.16` | | [langchain-core](https://github.com/langchain-ai/langchain) | `1.6.9` | `1.6.13` | | [langchain-openai](https://github.com/langchain-ai/langchain) | `1.7.0` | `1.7.2` | | [langchain-anthropic](https://github.com/langchain-ai/langchain) | `1.7.5` | `1.7.6` | | [google-genai](https://github.com/googleapis/python-genai) | `2.29.0` | `2.32.0` | | [langchain-protocol](https://github.com/langchain-ai/agent-protocol) | `0.0.19` | `0.0.20` | Updates `langchain` from 1.4.4 to 1.4.7 - [Release notes](https://github.com/langchain-ai/langchain/releases) - [Commits](https://github.com/langchain-ai/langchain/compare/langchain==1.4.4...langchain==1.4.7) Updates `langgraph` from 1.2.14 to 1.2.16 - [Release notes](https://github.com/langchain-ai/langgraph/releases) - [Commits](https://github.com/langchain-ai/langgraph/compare/1.2.14...1.2.16) Updates `langchain-core` from 1.6.9 to 1.6.13 - [Release notes](https://github.com/langchain-ai/langchain/releases) - [Commits](https://github.com/langchain-ai/langchain/compare/langchain-core==1.6.9...langchain-core==1.6.13) Updates `langchain-openai` from 1.7.0 to 1.7.2 - [Release notes](https://github.com/langchain-ai/langchain/releases) - [Commits](https://github.com/langchain-ai/langchain/compare/langchain-openai==1.7.0...langchain-openai==1.7.2) Updates `langchain-anthropic` from 1.7.5 to 1.7.6 - [Release notes](https://github.com/langchain-ai/langchain/releases) - [Commits](https://github.com/langchain-ai/langchain/compare/langchain-anthropic==1.7.5...langchain-anthropic==1.7.6) Updates `google-genai` from 2.29.0 to 2.32.0 - [Release notes](https://github.com/googleapis/python-genai/releases) - [Changelog](https://github.com/googleapis/python-genai/blob/main/CHANGELOG.md) - [Commits](https://github.com/googleapis/python-genai/compare/v2.29.0...v2.32.0) Updates `langchain-protocol` from 0.0.19 to 0.0.20 - [Release notes](https://github.com/langchain-ai/agent-protocol/releases) - [Commits](https://github.com/langchain-ai/agent-protocol/compare/langchain-protocol==0.0.19...langchain-protocol==0.0.20) --- updated-dependencies: - dependency-name: langchain dependency-version: 1.4.7 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: ai-providers - dependency-name: langgraph dependency-version: 1.2.16 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: ai-providers - dependency-name: langchain-core dependency-version: 1.6.13 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: ai-providers - dependency-name: langchain-openai dependency-version: 1.7.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: ai-providers - dependency-name: langchain-anthropic dependency-version: 1.7.6 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: ai-providers - dependency-name: google-genai dependency-version: 2.32.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: ai-providers - dependency-name: langchain-protocol dependency-version: 0.0.20 dependency-type: indirect update-type: version-update:semver-patch dependency-group: ai-providers ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> (cherry picked from commit c9105c5be181b3e81737c7481dc92f68f0cafcf7) --- uv.lock | 60 ++++++++++++++++++++++++++++++--------------------------- 1 file changed, 32 insertions(+), 28 deletions(-) diff --git a/uv.lock b/uv.lock index 60140fbd..993c15fe 100644 --- a/uv.lock +++ b/uv.lock @@ -1288,7 +1288,7 @@ requests = [ [[package]] name = "google-genai" -version = "2.29.0" +version = "2.32.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, @@ -1302,9 +1302,9 @@ dependencies = [ { name = "typing-extensions" }, { name = "websockets" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/21/2b/977616ef350e51d3d263eb43be23a91be793544cd937b51c0fac313be233/google_genai-2.29.0.tar.gz", hash = "sha256:124d3eef9ff6b9e4ea548f95ff5b7721683e7990c0a936b006d9e2ab61ea897d", size = 706902, upload-time = "2026-10-07T22:22:57.153Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d3/f9/89c95879bd58f176f82415c1a63a9dce528ea2b7a83a155412db5b1c783b/google_genai-2.32.0.tar.gz", hash = "sha256:9e8fb72f0a878291bc20b941d5d535e249f5729290565b7d49406827a0c30152", size = 750015, upload-time = "2026-10-21T00:38:45.526Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b8/27/a3dede4a1deb21f7fde6a171daa039bf0ea149a5e7826a785e8f109a6e13/google_genai-2.29.0-py3-none-any.whl", hash = "sha256:7adda55cb6746fb46d38ad3202c3372840b96eede964b5fb3df1469cd4998570", size = 1167603, upload-time = "2026-10-07T22:22:55.215Z" }, + { url = "https://files.pythonhosted.org/packages/97/70/964baf520f36019ad4e4ce316e6dc943189f3b398654ff8277dadb764f6d/google_genai-2.32.0-py3-none-any.whl", hash = "sha256:cf71a629df18510cd837cacb4843b38a2db6834c69eb93963a0d8ee4d657b725", size = 1213907, upload-time = "2026-10-21T00:38:43.471Z" }, ] [[package]] @@ -1797,30 +1797,30 @@ wheels = [ [[package]] name = "langchain" -version = "1.4.4" +version = "1.4.7" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "langchain-core" }, { name = "langgraph" }, { name = "pydantic" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6a/a4/a3ceefc3ec734912ca3a21dd79a5e4a98af73015a87e64d284a92cd5572c/langchain-1.4.4.tar.gz", hash = "sha256:baac6e13c08460e1decd0e60061b9a87c07135f1c977424040b0b57ff7bc7887", size = 742902, upload-time = "2026-10-08T22:14:10.86Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d1/6b/7147b4cfa86091e05e3c0189952d976e13ef27249c8cd9e46d8c742a5b5c/langchain-1.4.7.tar.gz", hash = "sha256:818275348cd707f7fbbe92ece0a725c25823140a054cf29865f420e0200fa2af", size = 762840, upload-time = "2026-10-21T21:39:01.141Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/02/36/c348f812eb37a137de2c80140ed1324ab11b75fa5a845e3f1035bacaac4a/langchain-1.4.4-py3-none-any.whl", hash = "sha256:2fd27e9a2d54ebcd915ac5ea51d79c772e020567ef6c6a76678182055489b804", size = 165363, upload-time = "2026-10-08T22:14:09.321Z" }, + { url = "https://files.pythonhosted.org/packages/93/2e/86a102e61fc88ede073aebdb795fef0eb058a887bed4aa862e8ca2697565/langchain-1.4.7-py3-none-any.whl", hash = "sha256:bc40d4a9a46f0c3d75e316c0e576931fe19bceef83912a71dc1e0df781eb8f82", size = 170549, upload-time = "2026-10-21T21:38:59.466Z" }, ] [[package]] name = "langchain-anthropic" -version = "1.7.5" +version = "1.7.6" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anthropic" }, { name = "langchain-core" }, { name = "pydantic" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/91/e6/45ead93ef6d9f244b502a6a32bf9a185b3da1cc7fa8003896a102dadac09/langchain_anthropic-1.7.5.tar.gz", hash = "sha256:6016f38e5c4bf12c16ecfa76abf133a978f459f4d2887b8e28435ae1ff00beb3", size = 764625, upload-time = "2026-09-29T15:22:07.106Z" } +sdist = { url = "https://files.pythonhosted.org/packages/3c/45/b369666909be4a6f58877b18e692655ee792e06047c6c572d1218e09a31c/langchain_anthropic-1.7.6.tar.gz", hash = "sha256:e5b41959a39810e69bc608fd6622b7d5d017bde93f858b09332fbb6ac764c393", size = 765188, upload-time = "2026-10-12T14:39:58.537Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/eb/a8/c507f2c1859b584843983e98bc71e0066c16a77e6c181921c928e0fdbcf7/langchain_anthropic-1.7.5-py3-none-any.whl", hash = "sha256:b21ef0e0c2d6175235a5b7eef4f5f89057df92d42dbfab66920a05605ef7173a", size = 65239, upload-time = "2026-09-29T15:22:05.832Z" }, + { url = "https://files.pythonhosted.org/packages/97/d6/da1ba42b7c26cd511c9be5473dbb178c51b07a337e94f09188d45f0306a2/langchain_anthropic-1.7.6-py3-none-any.whl", hash = "sha256:06bee97006d99e9a04abc4bd56599fc356c0c7f7f2a97629645cb603d9785c73", size = 65535, upload-time = "2026-10-12T14:39:57.386Z" }, ] [[package]] @@ -1867,7 +1867,7 @@ wheels = [ [[package]] name = "langchain-core" -version = "1.6.9" +version = "1.6.13" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "httpx" }, @@ -1881,14 +1881,14 @@ dependencies = [ { name = "typing-extensions" }, { name = "uuid-utils" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a7/d3/e9611e44e62ab8175127e137587c62dc2ed00e2615aebd89dfdb77f5fba0/langchain_core-1.6.9.tar.gz", hash = "sha256:34394228603e6f0bd24ce4e773b58b7d160e1b8cf489ecb350cf41f5366f5e92", size = 1010051, upload-time = "2026-10-08T20:11:19.881Z" } +sdist = { url = "https://files.pythonhosted.org/packages/40/76/0b8e5383a9c6a05bde284790d661487bb586cf101f43e8d4dac1fb3da1ee/langchain_core-1.6.13.tar.gz", hash = "sha256:756d8fd971cd7e3a5a19cf5453a81159351bd40cbfe62cbfe7e2ee3941008772", size = 1018477, upload-time = "2026-10-21T20:43:32.248Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/49/41/fdcdd869c052874b17e2325c986cf8c2fc6bbefa3b9d5404dd64dfffb12a/langchain_core-1.6.9-py3-none-any.whl", hash = "sha256:2bacea12270fd4cfdeeefdc5977e7e8b3c30814c509534d1ef43f4e5b7d3f657", size = 572951, upload-time = "2026-10-08T20:11:18.189Z" }, + { url = "https://files.pythonhosted.org/packages/b0/2f/c15b026e7b4aa093bc237c909bf909e8a59143b1315efee43c296fad58ce/langchain_core-1.6.13-py3-none-any.whl", hash = "sha256:8605de6f84a49902ef6be03d6fe2a126e7dfc35184315989689f341181f0a8fb", size = 574683, upload-time = "2026-10-21T20:43:30.951Z" }, ] [[package]] name = "langchain-openai" -version = "1.7.0" +version = "1.7.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "certifi" }, @@ -1896,21 +1896,21 @@ dependencies = [ { name = "openai" }, { name = "tiktoken" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/34/68/dd7f7ef76efe58222d37f1b1a5595b1bb368fc9165f71526db26e4c5759e/langchain_openai-1.7.0.tar.gz", hash = "sha256:7df2db16d32320ba563b7e4291a71ec6bf7b0ea8a5369ed2a0501d74928ea6d0", size = 3404806, upload-time = "2026-10-08T16:07:35.135Z" } +sdist = { url = "https://files.pythonhosted.org/packages/da/ca/b64799a9f2ee688aa6c238f5230b20d150b19cc5892bdcb6faf2b59866f1/langchain_openai-1.7.2.tar.gz", hash = "sha256:e1fcbc7a96f7c8c94679d61639a979bba88f27889c0a68dfa225b8057345328d", size = 3405949, upload-time = "2026-10-14T13:50:59.956Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b6/ef/3f40387231281eaefb0bec31bfa86a7d533de9d90b46802c3e24d436a163/langchain_openai-1.7.0-py3-none-any.whl", hash = "sha256:a4d319385019c010c579b39e8951e4d5ededeacf94cd9c79162f4cb7ecba50fb", size = 145622, upload-time = "2026-10-08T16:07:33.119Z" }, + { url = "https://files.pythonhosted.org/packages/39/5d/8c4888a21087e4cd1c32965613a1a17cfc8e90e41ba5205c3dad302f3410/langchain_openai-1.7.2-py3-none-any.whl", hash = "sha256:9be7980edf355ca2dd595b795a5125836ccf04515ef9377228913246156e59f5", size = 146367, upload-time = "2026-10-14T13:50:58.697Z" }, ] [[package]] name = "langchain-protocol" -version = "0.0.19" +version = "0.0.20" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/14/56/913599f2f9cec8524868929f12d72b2ede377a6056ca8a40a32bdadfa535/langchain_protocol-0.0.19.tar.gz", hash = "sha256:79d90a1425122ac87e8052e2ec054fbd09c3edbf341bdfb6397112a495c7bf8c", size = 6265, upload-time = "2026-08-26T21:12:00.703Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cb/8b/d908d082c3cf5368ddd1816ed114de79c873f979ba7f98b95fab0e841643/langchain_protocol-0.0.20.tar.gz", hash = "sha256:0f5bd002474265648e91161771a9e0438fe9480bd55a416e51af0f60070637ec", size = 6280, upload-time = "2026-10-20T11:13:50.84Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/80/c9/f6cbf357d48ccbd18bb394433b1fd7ad9be004eed9377ad08bb85777e5e6/langchain_protocol-0.0.19-py3-none-any.whl", hash = "sha256:4cdf879a492a35980fd859ae792d3c65458ccaae504e183c9a10d7eac1f0720f", size = 7327, upload-time = "2026-08-26T21:11:59.781Z" }, + { url = "https://files.pythonhosted.org/packages/02/4b/f1c99a0b27c5ff8b4b131c0d6e62012c17cc424089efe213767db927afd2/langchain_protocol-0.0.20-py3-none-any.whl", hash = "sha256:3c935e5e46b0f6f9cb52f45ba7cfa53d1b9fa80c82c4a277d47f03e07f7385b1", size = 7347, upload-time = "2026-10-20T11:13:49.566Z" }, ] [[package]] @@ -1927,7 +1927,7 @@ wheels = [ [[package]] name = "langgraph" -version = "1.2.14" +version = "1.2.16" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "langchain-core" }, @@ -1937,22 +1937,22 @@ dependencies = [ { name = "pydantic" }, { name = "xxhash" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/49/3f/6e60789eab2c5ceaf34ba2c626ac345df93b026bbe7385018aa4b9c30397/langgraph-1.2.14.tar.gz", hash = "sha256:5b1a72b7a7f994ac6947b7751fbbe00e949f8bb44d9ab78881604b703247ae55", size = 752793, upload-time = "2026-10-06T14:41:19.789Z" } +sdist = { url = "https://files.pythonhosted.org/packages/db/65/69de99a24546c1e3b75a67905dbcc17ed6cd0ed81694d4cc021ed688a60d/langgraph-1.2.16.tar.gz", hash = "sha256:e4dd63480bfd3ee14a6ab80ecf1311698780519693d8e3b67a28f3486e69e1f7", size = 772613, upload-time = "2026-10-16T22:11:14.147Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ec/4a/f976df0d3cf7fb7986dc39f656c6b7b5655834430e9bdfaef1e1e4fffaba/langgraph-1.2.14-py3-none-any.whl", hash = "sha256:e3f5f8ff4259f9281b73ff50fc9ef5cc3b025b8936b8283ecdca7cc0c4128690", size = 255980, upload-time = "2026-10-06T14:41:18.129Z" }, + { url = "https://files.pythonhosted.org/packages/ad/eb/ceb9bed99f6549e9ec51c497b749062d19e0a1c68fea0ad801d55c4a9691/langgraph-1.2.16-py3-none-any.whl", hash = "sha256:aeceb1bc49488409f12b02f1e51881099391d9e85bcfd78e1dbead71831c41b5", size = 260357, upload-time = "2026-10-16T22:11:12.638Z" }, ] [[package]] name = "langgraph-checkpoint" -version = "4.1.1" +version = "4.3.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "langchain-core" }, { name = "ormsgpack" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/83/47/886af6f886f0bff2273164a45f008694e48a96ff3cd25ff0228f2aa9480e/langgraph_checkpoint-4.1.1.tar.gz", hash = "sha256:6c2bdb530c91f91d7d9c1bd100925d0fc4f498d418c17f3587d1526279482a25", size = 184020, upload-time = "2026-05-22T16:57:38.503Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0f/69/31fdbdc65a85bbd6178afa193c772bb926620f47b4869638bc2bc80afaaa/langgraph_checkpoint-4.3.0.tar.gz", hash = "sha256:c75965d84cc2c1d549163e910a15bcb577758001b141619d05297c463280b018", size = 182652, upload-time = "2026-10-12T22:26:31.478Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/bd/b4/71425e3e38be92611300b9cc5e46a5bf98ab23f5ea8a75b73d02a2f1413c/langgraph_checkpoint-4.1.1-py3-none-any.whl", hash = "sha256:25d29144b082827218e7bc3f1e9b0566a4bb007895cd6cc26f66a8428739f56e", size = 56212, upload-time = "2026-05-22T16:57:37.203Z" }, + { url = "https://files.pythonhosted.org/packages/1f/0c/84747e340bf4f29291c84cdd5733fc8d0a822f3d33bb24e664a18afa4a7c/langgraph_checkpoint-4.3.0-py3-none-any.whl", hash = "sha256:bedfafe2f997ded60e4fa593e79f56f436a6e45586392dc382aa810d0c751c64", size = 58063, upload-time = "2026-10-12T22:26:30.429Z" }, ] [[package]] @@ -1986,23 +1986,27 @@ wheels = [ [[package]] name = "langsmith" -version = "0.8.18" +version = "0.14.12" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "httpx" }, + { name = "anyio" }, + { name = "distro" }, + { name = "httpx2" }, { name = "orjson", marker = "platform_python_implementation != 'PyPy'" }, { name = "packaging" }, { name = "pydantic" }, { name = "requests" }, { name = "requests-toolbelt" }, + { name = "sniffio" }, + { name = "typing-extensions" }, { name = "uuid-utils" }, { name = "websockets" }, { name = "xxhash" }, { name = "zstandard" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9a/d9/a6681aa9847bbbc5ec21abe20a5e233b94e5edcfe39624db607ac7e8ccb4/langsmith-0.8.18.tar.gz", hash = "sha256:32dde9c0e67e053e0fb738921fc8ced768af7b8fa83d7a0e3fd63597cf8776dd", size = 4526988, upload-time = "2026-06-19T13:12:17.123Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/f7/07a02241b6e6ed3df4e31a64ed09cb522c47227816da3d71097e7cf94657/langsmith-0.14.12.tar.gz", hash = "sha256:b4d6a24ed74333095da476803ad9d22ee35f9dae1052cebf1e366d3889a9b675", size = 5038994, upload-time = "2026-10-21T20:16:22.565Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/03/70/0e0cc80a3b064c8d6c8d697c3125ed86e39d5a7393ec6dc8b07cb1cf13c4/langsmith-0.8.18-py3-none-any.whl", hash = "sha256:3940183349993faef48e6c7d08e4822ee9cefd906b362d0e3c2d650314d2f282", size = 508108, upload-time = "2026-06-19T13:12:15.348Z" }, + { url = "https://files.pythonhosted.org/packages/c6/6c/33e2de8d8d809895263e4f24ef7619e5535e3ece7de863ffbe49c1b7a60d/langsmith-0.14.12-py3-none-any.whl", hash = "sha256:9dafba7f615afa77204dc8afbb9c803e4ba1b55e96249f12c8f9aafb23fae6d0", size = 883644, upload-time = "2026-10-21T20:16:20.039Z" }, ] [[package]] From ac35d8684ff5e80cf5fac6da1a20bb4271f13210 Mon Sep 17 00:00:00 2001 From: Anders <754494+andehen@users.noreply.github.com> Date: Fri, 30 Oct 2026 09:47:26 +0100 Subject: [PATCH 53/60] ci: pin Python 3.11 by minor version for Ubuntu 26.04 runners (#1153) (cherry picked from commit 50cf3573f2af46455c1a313e1649649f9d671180) --- .github/actions/setup-python-dev/action.yml | 2 +- .github/workflows/ci.yml | 6 +++--- .github/workflows/release.yml | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/actions/setup-python-dev/action.yml b/.github/actions/setup-python-dev/action.yml index 347ebeaf..5739fa55 100644 --- a/.github/actions/setup-python-dev/action.yml +++ b/.github/actions/setup-python-dev/action.yml @@ -5,7 +5,7 @@ inputs: python-version: description: Python version to install. required: false - default: 3.11.11 + default: 3.11 runs: using: composite diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 92334514..0a605fc1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -89,7 +89,7 @@ jobs: - name: Set up Python 3.11 uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: - python-version: 3.11.11 + python-version: 3.11 - name: Check strict downstream Pyright usage run: .github/scripts/check_strict_types.sh @@ -106,7 +106,7 @@ jobs: - name: Set up Python 3.11 uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: - python-version: 3.11.11 + python-version: 3.11 - name: Install uv uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0 @@ -238,7 +238,7 @@ jobs: if: steps.changes.outputs.changed == 'true' uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: - python-version: 3.11.11 + python-version: 3.11 - name: Install uv if: steps.changes.outputs.changed == 'true' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e8afa85b..0dd57cb0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -98,7 +98,7 @@ jobs: - name: Set up Python uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: - python-version: 3.11.11 + python-version: 3.11 - name: Install uv uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0 @@ -303,7 +303,7 @@ jobs: - name: Set up Python uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: - python-version: 3.11.11 + python-version: 3.11 - name: Install uv uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0 @@ -470,7 +470,7 @@ jobs: - name: Set up Python uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: - python-version: 3.11.11 + python-version: 3.11 - name: Install uv uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0 From c1ee43ecd5493c961bd7872602b400736c691477 Mon Sep 17 00:00:00 2001 From: Harley Alexander <43975092+mayteio@users.noreply.github.com> Date: Fri, 30 Oct 2026 10:05:42 +0000 Subject: [PATCH 54/60] feat: send transactional email from posthog templates (#1093) * feat: send transactional email from posthog templates Add posthog.emails.send() on Client and AsyncClient, plus standalone Emails and AsyncEmails in posthog.emails for processes that only send email. Sends go to the app host with a project secret API key and an Idempotency-Key, and retry network errors, rate limits, an in-flight key and 503 with the same key. Errors raise EmailError with PostHog's code and a Resend-style name. Co-Authored-By: Claude Opus 5.5 Generated-By: PostHog Desktop Task-Id: c62b30cd-eb2e-496c-a73d-2a6e7a52a62e * fix: keep the email secret key off custom hosts and close email transports client.emails only infers PostHog Cloud app hosts. A custom host raises app_host_required before any request, so an ingestion proxy never receives the project secret key. A kept emails reference now reads the client's disabled state on every send. Shutdown closes the email transport, a forked child drops the parent's email session, and the retry ceiling applies after jitter. Docs examples use client.emails. Co-Authored-By: Claude Opus 5.5 Generated-By: PostHog Desktop Task-Id: c62b30cd-eb2e-496c-a73d-2a6e7a52a62e * fix: refuse email sends after shutdown and drain async sends first client.emails now asks the parent client to admit each send. After shutdown, a kept or new client.emails raises EmailError with code client_closed and makes no request. On AsyncClient, an admitted send is an in-flight operation, so shutdown waits for it, retries included, before it closes the email transport. Co-Authored-By: Claude Opus 5.5 Generated-By: PostHog Desktop Task-Id: c62b30cd-eb2e-496c-a73d-2a6e7a52a62e * feat: configure email sends on the client and document setup Posthog and AsyncPosthog take app_host, email_request_timeout_seconds and email_request_max_retries, so client.emails works behind a custom host and a job runner can turn retries off. Emails takes a session and AsyncEmails an http_client, which gives tests a supported no-network path. The posthog.emails module docs now hold a setup quickstart: keys and scope, template ID, hosts, retries, errors to expect, cleanup and testing. The release note names client.emails.send(). Co-Authored-By: Claude Opus 5.5 Generated-By: PostHog Desktop Task-Id: c62b30cd-eb2e-496c-a73d-2a6e7a52a62e * fix: match the email contract's missing_app_host and keep network causes - A non-Cloud host without app_host now fails with missing_app_host, the code the cross-SDK contract in PostHog/posthog#120921 defines, matching posthog-node. - A network_error keeps the requests or httpx exception as __cause__, so a failed send is easier to debug. - Regenerate the public API snapshot after the rebase onto main. Co-Authored-By: Claude Opus 5.5 Generated-By: PostHog Desktop Task-Id: e72a2c10-f145-4f7c-8754-a4fcc06a18be * fix: keep the bearer token and send timeout on injected async clients An httpx.AsyncClient passed as http_client can carry its own auth and timeout. Its auth replaced the email's Bearer header, and its timeout replaced the send timeout, so a client built with timeout=None left sends unbounded. Each async send now passes auth=None and timeout=self.timeout. Co-Authored-By: Claude Opus 5.5 Generated-By: PostHog Desktop Task-Id: e72a2c10-f145-4f7c-8754-a4fcc06a18be * fix: name 422 send checks validation_error The template send route answers 422 for recipient_suppressed, sending_paused and sending_suspended as well as idempotency_key_reused. Only idempotency_key_reused is now invalid_idempotent_request; the send checks are validation_error, as the cross-SDK contract names them and posthog-node does. Co-Authored-By: Claude Opus 5.5 Generated-By: PostHog Desktop Task-Id: e72a2c10-f145-4f7c-8754-a4fcc06a18be * fix: raise client_disabled for email sends from a disabled client A disabled client returned SendEmailResult(id=None), so a caller that treats a returned result as sent dropped the email silently. It now sends nothing and raises EmailError with code client_disabled (name application_error), as the cross-SDK contract in PostHog/posthog#120921 defines and posthog-node does. SendEmailResult.id is always the queued message ID, so its type is str. Co-Authored-By: Claude Opus 5.5 Generated-By: PostHog Desktop Task-Id: e72a2c10-f145-4f7c-8754-a4fcc06a18be * feat: send transactional email through the single send endpoint `emails.send()` now posts to `/api/projects/@current/messaging/send/email/` with the template ID in the body as `template.id`, instead of the template-scoped route. Co-Authored-By: Claude Opus 5.5 Generated-By: PostHog Desktop Task-Id: e72a2c10-f145-4f7c-8754-a4fcc06a18be * feat: read a transactional email's status with emails.get `emails.get(message_id)` on `Emails` and `AsyncEmails` reads `/api/projects/@current/messaging/emails/{id}/` and returns an `EmailStatus` with `status`, `created_at`, `status_at` and `reason`. It retries network errors, rate limits and an unavailable service like `send`, which now shares that attempt loop. Co-Authored-By: Claude Opus 5.5 Generated-By: PostHog Desktop Task-Id: e72a2c10-f145-4f7c-8754-a4fcc06a18be * docs: describe the email error name as a category Co-Authored-By: Claude Opus 5.5 Generated-By: PostHog Desktop Task-Id: e72a2c10-f145-4f7c-8754-a4fcc06a18be * feat: add a per-send max_retries to emails.send `send(max_retries=...)` on `Emails` and `AsyncEmails` overrides the client's retry limit for one send, like posthog-node's `maxRetries`. A negative or non-integer value raises `ValueError` before any request. Co-Authored-By: Claude Opus 5.5 Generated-By: PostHog Desktop Task-Id: e72a2c10-f145-4f7c-8754-a4fcc06a18be (cherry picked from commit 01e91db94185969f636d501a58a21eb9f2c3721e) --- .sampo/changesets/transactional-emails.md | 5 + posthog/async_client.py | 60 +- posthog/client.py | 62 ++ posthog/emails.py | 942 ++++++++++++++++++++++ posthog/request.py | 13 + posthog/test/test_emails.py | 574 +++++++++++++ references/public_api_snapshot.txt | 49 +- typings/requests/__init__.pyi | 2 + 8 files changed, 1704 insertions(+), 3 deletions(-) create mode 100644 .sampo/changesets/transactional-emails.md create mode 100644 posthog/emails.py create mode 100644 posthog/test/test_emails.py diff --git a/.sampo/changesets/transactional-emails.md b/.sampo/changesets/transactional-emails.md new file mode 100644 index 00000000..21c9e6c7 --- /dev/null +++ b/.sampo/changesets/transactional-emails.md @@ -0,0 +1,5 @@ +--- +pypi/posthog: minor +--- + +Send transactional email from PostHog email templates with `client.emails.send()` on `Posthog` and `AsyncPosthog`, or with `Emails` and `AsyncEmails` from `posthog.emails` in a process that only sends email. A send needs a project secret API key, goes to the app host, sends an idempotency key, and retries network errors, rate limits and an unavailable service with the same key. Set `app_host`, `email_request_timeout_seconds` and `email_request_max_retries` on the client to configure sends, or `max_retries` on one send. Errors raise `EmailError` with PostHog's error `code` and a category `name`. Read what happened to a sent email with `client.emails.get(message_id)`, which returns an `EmailStatus` with `status`, `created_at`, `status_at` and `reason` for 30 days after the send. diff --git a/posthog/async_client.py b/posthog/async_client.py index 766c56bb..9ecef658 100644 --- a/posthog/async_client.py +++ b/posthog/async_client.py @@ -11,7 +11,7 @@ import weakref from dataclasses import replace from datetime import datetime, timezone -from typing import Any, Dict, Mapping, Optional, Union +from typing import Any, Callable, Dict, Mapping, Optional, Union from uuid import UUID from typing_extensions import Unpack @@ -90,6 +90,7 @@ try_attach_code_variables_to_frames, ) from .consumer import AI_MAX_MSG_SIZE, MAX_MSG_SIZE +from .emails import AsyncEmails, _client_emails, _validate_options from .feature_flag_evaluations import ( FeatureFlagEvaluations, _EvaluatedFlagRecord, @@ -170,6 +171,9 @@ def __init__( personal_api_key: Optional[str] = None, feature_flags_request_timeout_seconds: float = 3, feature_flags_request_max_retries: int = 1, + app_host: Optional[str] = None, + email_request_timeout_seconds: float = 15, + email_request_max_retries: int = 2, ) -> None: if flush_at <= 0: raise ValueError("flush_at must be greater than zero") @@ -230,6 +234,10 @@ def __init__( else resolved_secret_key ) or None self.personal_api_key = self.secret_key + _validate_options(email_request_timeout_seconds, email_request_max_retries) + self.app_host = app_host + self.email_request_timeout_seconds = email_request_timeout_seconds + self.email_request_max_retries = email_request_max_retries self.feature_flags_request_timeout_seconds = ( feature_flags_request_timeout_seconds ) @@ -326,6 +334,49 @@ def __init__( self._duplicate_client_registry_key: Optional[tuple[str, str]] = None self._register_duplicate_client() + _emails: Optional[AsyncEmails] = None + + @property + def emails(self) -> AsyncEmails: + """ + Send transactional email from PostHog email templates, and read the + status of each email sent with ``get``. + + Needs ``secret_key``, a project secret API key with the ``hog_flow:write`` + scope, and ``posthog[async]``. Sends go to ``app_host``, or to the PostHog + Cloud app host of ``host``. ``email_request_timeout_seconds`` and + ``email_request_max_retries`` configure each send. A disabled or shut-down + client sends nothing. See ``posthog.emails`` for setup. + + Examples: + ```python + await client.emails.send( + to="user@example.com", + template_id="0190f6d2-7c1e-7b3a-9f5e-1c2d3e4f5a6b", + distinct_id="user_123", + ) + ``` + """ + if self._emails is None: + self._emails = _client_emails( + AsyncEmails, + self.secret_key, + self.host, + lambda: self.disabled, + self._admit_email_send, + app_host=self.app_host, + timeout=self.email_request_timeout_seconds, + max_retries=self.email_request_max_retries, + ) + return self._emails + + def _admit_email_send(self) -> Optional[Callable[[], None]]: + # Shutdown waits for in-flight operations before it closes the email transport. + if not self._accepting or self._closed: + return None + completion = self._start_inflight_operation() + return lambda: self._finish_inflight_operation(completion) + async def __aenter__(self) -> AsyncClient: self._ensure_workers_started(self._analytics_lane) return self @@ -1410,6 +1461,13 @@ async def shutdown(self) -> None: self.log.exception("Failed to close async capture transport") errors.append(error) + if self._emails is not None: + try: + await self._emails.aclose() + except Exception as error: + self.log.exception("Failed to close the async email transport") + errors.append(error) + try: self._unregister_duplicate_client() except Exception as error: diff --git a/posthog/client.py b/posthog/client.py index 6b078616..c59a78e1 100644 --- a/posthog/client.py +++ b/posthog/client.py @@ -74,6 +74,7 @@ set_context_session as _context_set_context_session, tag as _context_tag, ) +from posthog.emails import Emails, _client_emails, _no_op, _validate_options from posthog.exception_capture import ExceptionCapture from posthog._logging import _configure_posthog_logging from posthog.exception_utils import ( @@ -791,6 +792,9 @@ def __init__( _use_ai_lane=False, _enable_multimodal_capture=False, traces: Optional[dict] = None, + app_host: Optional[str] = None, + email_request_timeout_seconds: float = 15, + email_request_max_retries: int = 2, ): """ Initialize a new PostHog client instance. @@ -849,6 +853,15 @@ def __init__( feature_flags_request_max_retries: Number of retries for feature flag requests after network, transport, or timeout failures. Defaults to 1. Set to 0 to disable retries. + app_host: Your PostHog app host, such as ``https://posthog.example.com``. + ``emails`` sends there. Needed only when ``host`` is not PostHog + Cloud, for example a reverse proxy, which must not receive the + secret key. + email_request_timeout_seconds: Timeout in seconds for each email send + request. Defaults to 15. + email_request_max_retries: Number of retries for an email send after a + network error, a rate limit, or an unavailable service. Defaults to 2. + Set to 0 when a job runner already retries the send. super_properties: Properties for every captured event. They fill only keys the event leaves unset, before ``before_send`` runs. ``$set``, ``$set_once``, ``$groups`` and ``$group_set`` fill @@ -1160,6 +1173,10 @@ def __init__( else resolved_secret_key ) or None self.personal_api_key = self.secret_key + _validate_options(email_request_timeout_seconds, email_request_max_retries) + self.app_host = app_host + self.email_request_timeout_seconds = email_request_timeout_seconds + self.email_request_max_retries = email_request_max_retries if debug: # Ensures that debug level messages are logged when debug mode is on. # Otherwise, defaults to WARNING level. See https://docs.python.org/3/howto/logging.html#what-happens-if-no-configuration-is-provided @@ -1320,6 +1337,43 @@ def run_as_callback(sample): before_send=before_send, ) + _emails: Optional[Emails] = None + + @property + def emails(self) -> Emails: + """ + Send transactional email from PostHog email templates, and read the + status of each email sent with ``get``. + + Needs ``secret_key``, a project secret API key with the ``hog_flow:write`` + scope. Sends go to ``app_host``, or to the PostHog Cloud app host of + ``host``. ``email_request_timeout_seconds`` and ``email_request_max_retries`` + configure each send. A disabled or shut-down client sends nothing. See + ``posthog.emails`` for setup. + + Examples: + ```python + client.emails.send( + to="user@example.com", + template_id="0190f6d2-7c1e-7b3a-9f5e-1c2d3e4f5a6b", + template_data={"name": "Ada"}, + distinct_id="user_123", + ) + ``` + """ + if self._emails is None: + self._emails = _client_emails( + Emails, + self.secret_key, + self.host, + lambda: self.disabled, + lambda: None if self._shutdown_requested else _no_op, + app_host=self.app_host, + timeout=self.email_request_timeout_seconds, + max_retries=self.email_request_max_retries, + ) + return self._emails + @property def queue(self) -> Queue: """The analytics lane's queue (kept for backwards compatibility).""" @@ -2547,6 +2601,8 @@ def _reinit_after_fork(self): self._lifecycle_lock = threading.Lock() self._lifecycle_condition = threading.Condition(self._lifecycle_lock) self._lifecycle_owner = None + # The parent's email session and its lock are not safe to share with the child. + self._emails = None self._deferred_lifecycle_thread_pending = False self._deferred_lifecycle_dirty = False self._lifecycle_callback_context = ContextVar( @@ -3298,6 +3354,12 @@ def _join_once( self._shutdown_flag_definition_cache_provider, errors, ) + if self._emails is not None: + self._run_lifecycle_cleanup( + "Failed to close the email transport during lifecycle cleanup", + self._emails.close, + errors, + ) self._run_lifecycle_cleanup( "Failed to unregister client during lifecycle cleanup", self._unregister_duplicate_client, diff --git a/posthog/emails.py b/posthog/emails.py new file mode 100644 index 00000000..b184d83f --- /dev/null +++ b/posthog/emails.py @@ -0,0 +1,942 @@ +"""Transactional email, sent from an email template in PostHog. + +Setup: + +1. In PostHog, open the email template under Workflows, make it transactional, + and copy its ID. ``template_id`` is that UUID. +2. Create a project secret API key (``phs_...``) with Workflow write access + (``hog_flow:write``) in your project settings. The project API key + (``phc_...``) captures events but cannot send email. +3. Send from the client you already use, or from :class:`Emails` in a process + that only sends email: + +```python +from posthog import Posthog + +posthog = Posthog("phc_...", secret_key="phs_...", host="https://eu.i.posthog.com") +result = posthog.emails.send( + to="user@example.com", + template_id="