vybekiit 0.7.26 → 0.7.27
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.js +3891 -1301
- package/dist/global-skills/aws-cdk/SKILL.md +19 -5
- package/dist/global-skills/aws-cdk/references/fast-deployments.md +191 -0
- package/dist/global-skills/aws-cdk/references/troubleshooting-deployment.md +16 -0
- package/dist/global-skills/aws-cloudformation/SKILL.md +16 -26
- package/dist/global-skills/aws-cloudformation/references/check-cloudformation-template-compliance.script.md +7 -3
- package/dist/global-skills/aws-cloudformation/references/cloudformation-language-server.md +177 -0
- package/dist/global-skills/aws-cloudformation/references/cloudformation-pre-deploy-validation.script.md +8 -2
- package/dist/global-skills/aws-cloudformation/references/persist-template-context.script.md +5 -8
- package/dist/global-skills/aws-cloudformation/references/retrieve-template-context.script.md +1 -1
- package/dist/global-skills/aws-cloudformation/references/security-considerations.md +51 -0
- package/dist/global-skills/aws-cloudformation/references/troubleshoot-failed-stack.script.md +138 -0
- package/dist/global-skills/aws-cloudformation/references/{validate-cloudformation-template.script.md → validate-with-cfn-lint.script.md} +15 -27
- package/dist/global-skills/aws-cloudformation/references/validate-with-cloudformation-validate.script.md +181 -0
- package/dist/global-skills/aws-cloudformation/references/validation-tool-selection.md +44 -0
- package/dist/global-skills/aws-serverless/SKILL.md +9 -1
- package/dist/global-skills/aws-serverless/references/architecture.md +3 -1
- package/dist/global-skills/aws-serverless/references/lambda.md +3 -1
- package/dist/global-skills/aws-serverless/references/orchestration.md +1 -0
- package/dist/global-skills/better-auth-best-practices/SKILL.md +18 -8
- package/dist/global-skills/eas-app-stores/SKILL.md +31 -15
- package/dist/global-skills/eas-app-stores/agents/openai.yaml +2 -2
- package/dist/global-skills/eas-app-stores/references/ios-app-store.md +37 -32
- package/dist/global-skills/eas-app-stores/references/native-ios.md +167 -0
- package/dist/global-skills/eas-app-stores/references/play-store.md +3 -7
- package/dist/global-skills/eas-app-stores/references/testflight.md +39 -35
- package/dist/global-skills/eas-simulator/SKILL.md +48 -26
- package/dist/global-skills/eas-simulator/references/controllers.md +32 -3
- package/dist/global-skills/eas-simulator/references/run-your-app.md +34 -4
- package/dist/global-skills/eas-simulator/references/troubleshooting.md +8 -4
- package/dist/global-skills/eas-update/SKILL.md +146 -0
- package/dist/global-skills/eas-update/agents/openai.yaml +4 -0
- package/dist/global-skills/expo-animation/RECIPES.md +2 -2
- package/dist/global-skills/expo-animation/SKILL.md +9 -2
- package/dist/global-skills/expo-brownfield/SKILL.md +18 -11
- package/dist/global-skills/expo-brownfield/agents/openai.yaml +2 -2
- package/dist/global-skills/expo-brownfield/references/brownfield-integrated.md +94 -69
- package/dist/global-skills/expo-brownfield/references/brownfield-isolated.md +40 -42
- package/dist/global-skills/expo-brownfield/references/comparison.md +5 -5
- package/dist/global-skills/expo-brownfield/references/feature-integration.md +163 -0
- package/dist/global-skills/expo-brownfield/references/troubleshooting.md +17 -17
- package/dist/global-skills/expo-brownfield/references/version-compatibility.md +40 -0
- package/dist/global-skills/expo-data-fetching/SKILL.md +27 -6
- package/dist/global-skills/expo-design-system/SKILL.md +27 -7
- package/dist/global-skills/expo-design-system/references/audit.md +7 -2
- package/dist/global-skills/expo-design-system/references/native-slop.md +74 -0
- package/dist/global-skills/expo-examples/SKILL.md +0 -1
- package/dist/global-skills/expo-examples/references/catalog.md +1 -1
- package/dist/global-skills/expo-migrate-module/SKILL.md +21 -10
- package/dist/global-skills/expo-migrate-module/references/compatibility.md +80 -23
- package/dist/global-skills/expo-migrate-module/references/migration-map.md +162 -11
- package/dist/global-skills/expo-native-ui/SKILL.md +25 -16
- package/dist/global-skills/expo-native-ui/agents/openai.yaml +2 -2
- package/dist/global-skills/expo-native-ui/references/controls.md +5 -46
- package/dist/global-skills/expo-native-ui/references/icons.md +21 -2
- package/dist/global-skills/expo-native-ui/references/media.md +15 -20
- package/dist/global-skills/expo-native-ui/references/visual-effects.md +12 -11
- package/dist/global-skills/expo-overview/SKILL.md +17 -12
- package/dist/global-skills/expo-router/SKILL.md +5 -3
- package/dist/global-skills/expo-router/references/tabs.md +5 -5
- package/dist/global-skills/expo-upgrade/SKILL.md +3 -1
- package/dist/global-skills/expo-web-to-native/references/false-friends.md +2 -2
- package/dist/global-skills/expo-web-to-native/references/native-patterns.md +1 -1
- package/dist/global-skills/firebase-ai-logic-basics/SKILL.md +13 -16
- package/dist/global-skills/firebase-ai-logic-basics/references/ios_setup.md +4 -5
- package/dist/global-skills/firebase-ai-logic-basics/references/usage_patterns_android.md +4 -4
- package/dist/global-skills/firebase-ai-logic-basics/references/usage_patterns_web.md +3 -3
- package/dist/global-skills/firebase-auth-basics/SKILL.md +11 -6
- package/dist/global-skills/firebase-auth-basics/references/client_sdk_android.md +4 -5
- package/dist/global-skills/firebase-auth-basics/references/client_sdk_web.md +3 -3
- package/dist/global-skills/firebase-auth-basics/references/flutter_setup.md +24 -25
- package/dist/global-skills/firebase-auth-basics/references/security_rules.md +4 -2
- package/dist/global-skills/firebase-crashlytics/references/android_setup.md +7 -4
- package/dist/global-skills/firebase-crashlytics/references/ios_setup.md +2 -3
- package/dist/global-skills/firebase-data-connect/SKILL.md +2 -1
- package/dist/global-skills/firebase-data-connect/examples.md +4 -4
- package/dist/global-skills/firebase-data-connect/reference/config.md +5 -4
- package/dist/global-skills/firebase-data-connect/reference/realtime.md +1 -2
- package/dist/global-skills/firebase-data-connect/reference/sdk_flutter.md +2 -2
- package/dist/global-skills/firebase-data-connect/reference/sdk_ios.md +2 -2
- package/dist/global-skills/firebase-data-connect/reference/sdk_web.md +17 -6
- package/dist/global-skills/firebase-data-connect/reference/security.md +5 -5
- package/dist/global-skills/firebase-data-connect/templates.md +2 -1
- package/dist/global-skills/firebase-firestore/SKILL.md +20 -8
- package/dist/global-skills/firebase-firestore/references/enterprise/android_sdk_usage.md +5 -4
- package/dist/global-skills/firebase-firestore/references/enterprise/data_model.md +12 -3
- package/dist/global-skills/firebase-firestore/references/enterprise/indexes.md +16 -18
- package/dist/global-skills/firebase-firestore/references/enterprise/provisioning.md +1 -1
- package/dist/global-skills/firebase-firestore/references/enterprise/python_sdk_usage.md +5 -1
- package/dist/global-skills/firebase-firestore/references/enterprise/web_sdk_usage.md +7 -7
- package/dist/global-skills/firebase-firestore/references/standard/android_sdk_usage.md +5 -5
- package/dist/global-skills/firebase-firestore/references/standard/flutter_setup.md +4 -4
- package/dist/global-skills/firebase-firestore/references/standard/indexes.md +16 -18
- package/dist/global-skills/firebase-firestore/references/standard/provisioning.md +1 -1
- package/dist/global-skills/firebase-remote-config-basics/SKILL.md +0 -5
- package/dist/global-skills/firebase-remote-config-basics/references/android_setup.md +36 -8
- package/dist/global-skills/firebase-remote-config-basics/references/ios_setup.md +1 -7
- package/dist/global-skills/firebase-security-rules-auditor/SKILL.md +17 -6
- package/dist/global-skills/{firebase-firestore/references/standard/security_rules.md → firestore-rules-creation/SKILL.md} +24 -13
- package/dist/global-skills/grow-my-customers/SKILL.md +23 -0
- package/dist/global-skills/instrument-feature-flags/SKILL.md +25 -25
- package/dist/global-skills/instrument-feature-flags/references/adding-feature-flag-code.md +141 -285
- package/dist/global-skills/instrument-feature-flags/references/android.md +6 -15
- package/dist/global-skills/instrument-feature-flags/references/api.md +4 -11
- package/dist/global-skills/instrument-feature-flags/references/best-practices.md +1 -13
- package/dist/global-skills/instrument-feature-flags/references/django.md +14 -27
- package/dist/global-skills/instrument-feature-flags/references/dotnet.md +20 -79
- package/dist/global-skills/instrument-feature-flags/references/elixir.md +1 -9
- package/dist/global-skills/instrument-feature-flags/references/flask.md +13 -13
- package/dist/global-skills/instrument-feature-flags/references/flutter.md +3 -24
- package/dist/global-skills/instrument-feature-flags/references/go.md +3 -15
- package/dist/global-skills/instrument-feature-flags/references/ios.md +4 -17
- package/dist/global-skills/instrument-feature-flags/references/java.md +5 -13
- package/dist/global-skills/instrument-feature-flags/references/laravel.md +13 -17
- package/dist/global-skills/instrument-feature-flags/references/next-js.md +25 -32
- package/dist/global-skills/instrument-feature-flags/references/nodejs.md +8 -15
- package/dist/global-skills/instrument-feature-flags/references/php.md +1 -15
- package/dist/global-skills/instrument-feature-flags/references/python.md +2 -15
- package/dist/global-skills/instrument-feature-flags/references/react-native.md +13 -15
- package/dist/global-skills/instrument-feature-flags/references/react.md +17 -21
- package/dist/global-skills/instrument-feature-flags/references/ruby-on-rails.md +37 -83
- package/dist/global-skills/instrument-feature-flags/references/ruby.md +2 -15
- package/dist/global-skills/instrument-feature-flags/references/rust.md +13 -25
- package/dist/global-skills/instrument-feature-flags/references/usage.md +14 -63
- package/dist/global-skills/instrument-feature-flags/references/web.md +9 -14
- package/dist/global-skills/instrument-product-analytics/SKILL.md +29 -29
- package/dist/global-skills/instrument-product-analytics/references/EXAMPLE-astro-hybrid.md +3 -1
- package/dist/global-skills/instrument-product-analytics/references/EXAMPLE-astro-ssr.md +3 -1
- package/dist/global-skills/instrument-product-analytics/references/EXAMPLE-astro-static.md +3 -1
- package/dist/global-skills/instrument-product-analytics/references/EXAMPLE-astro-view-transitions.md +3 -1
- package/dist/global-skills/instrument-product-analytics/references/EXAMPLE-ruby-on-rails.md +3 -1
- package/dist/global-skills/instrument-product-analytics/references/android.md +72 -107
- package/dist/global-skills/instrument-product-analytics/references/angular.md +26 -28
- package/dist/global-skills/instrument-product-analytics/references/astro.md +13 -24
- package/dist/global-skills/instrument-product-analytics/references/configuration.md +45 -63
- package/dist/global-skills/instrument-product-analytics/references/django.md +14 -27
- package/dist/global-skills/instrument-product-analytics/references/dotnet.md +20 -79
- package/dist/global-skills/instrument-product-analytics/references/elixir.md +47 -49
- package/dist/global-skills/instrument-product-analytics/references/flask.md +13 -13
- package/dist/global-skills/instrument-product-analytics/references/flutter.md +60 -90
- package/dist/global-skills/instrument-product-analytics/references/go.md +17 -56
- package/dist/global-skills/instrument-product-analytics/references/identify-users.md +15 -15
- package/dist/global-skills/instrument-product-analytics/references/ios.md +11 -15
- package/dist/global-skills/instrument-product-analytics/references/laravel.md +13 -17
- package/dist/global-skills/instrument-product-analytics/references/next-js.md +25 -32
- package/dist/global-skills/instrument-product-analytics/references/nuxt-js-3-6.md +13 -27
- package/dist/global-skills/instrument-product-analytics/references/nuxt-js.md +14 -28
- package/dist/global-skills/instrument-product-analytics/references/php.md +33 -84
- package/dist/global-skills/instrument-product-analytics/references/posthog-python.md +229 -9
- package/dist/global-skills/instrument-product-analytics/references/python.md +415 -106
- package/dist/global-skills/instrument-product-analytics/references/react-native.md +161 -155
- package/dist/global-skills/instrument-product-analytics/references/react-router-v6.md +12 -33
- package/dist/global-skills/instrument-product-analytics/references/react-router-v7-data-mode.md +15 -33
- package/dist/global-skills/instrument-product-analytics/references/react-router-v7-declarative-mode.md +12 -33
- package/dist/global-skills/instrument-product-analytics/references/react-router-v7-framework-mode.md +26 -41
- package/dist/global-skills/instrument-product-analytics/references/ruby-on-rails.md +37 -83
- package/dist/global-skills/instrument-product-analytics/references/ruby.md +48 -108
- package/dist/global-skills/instrument-product-analytics/references/svelte.md +18 -24
- package/dist/global-skills/instrument-product-analytics/references/tanstack-start.md +17 -19
- package/dist/global-skills/instrument-product-analytics/references/usage.md +14 -63
- package/dist/global-skills/instrument-product-analytics/references/vue-js.md +29 -28
- package/dist/global-skills/manifest.json +8 -2
- package/dist/global-skills/mongodb-search-and-ai/SKILL.md +28 -37
- package/dist/global-skills/mongodb-search-and-ai/references/automated-embedding.md +438 -0
- package/dist/global-skills/mongodb-search-and-ai/references/hybrid-search.md +60 -4
- package/dist/global-skills/mongodb-search-and-ai/references/vector-search.md +46 -108
- package/dist/global-skills/neon/SKILL.md +207 -213
- package/dist/global-skills/neon/references/auth.md +12 -0
- package/dist/global-skills/neon/references/claimable-neon.md +10 -14
- package/dist/global-skills/neon/references/function-triggers.md +53 -0
- package/dist/global-skills/neon/references/logs-loki.md +61 -0
- package/dist/global-skills/neon/references/parse-env.md +32 -0
- package/dist/global-skills/neon/references/sdk.md +7 -0
- package/dist/global-skills/neon-ai-gateway/SKILL.md +14 -16
- package/dist/global-skills/neon-auth/SKILL.md +155 -0
- package/dist/global-skills/neon-auth/references/managed-auth.md +173 -0
- package/dist/global-skills/neon-auth/references/self-managed.md +25 -0
- package/dist/global-skills/neon-functions/SKILL.md +159 -84
- package/dist/global-skills/neon-functions/references/ai-sdk.md +4 -6
- package/dist/global-skills/neon-functions/references/function-triggers.md +249 -0
- package/dist/global-skills/neon-functions/references/mastra-studio.md +3 -3
- package/dist/global-skills/neon-functions/references/mcp.md +1 -1
- package/dist/global-skills/neon-functions/references/production-hardening.md +340 -0
- package/dist/global-skills/neon-functions/references/sse.md +8 -5
- package/dist/global-skills/neon-object-storage/SKILL.md +10 -11
- package/dist/global-skills/neon-postgres/SKILL.md +120 -17
- package/dist/global-skills/neon-postgres/references/full-text-search.md +99 -0
- package/dist/global-skills/neon-postgres/references/hybrid-search.md +90 -0
- package/dist/global-skills/neon-postgres/references/lakebase-search-drizzle.md +172 -0
- package/dist/global-skills/neon-postgres/references/vector-search.md +137 -0
- package/dist/global-skills/neon-postgres-branches/SKILL.md +3 -3
- package/dist/global-skills/neon-postgres-egress-optimizer/SKILL.md +1 -1
- package/dist/global-skills/onboarding/SKILL.md +8 -6
- package/dist/global-skills/resend/SKILL.md +4 -2
- package/dist/global-skills/resend/references/broadcasts.md +6 -1
- package/dist/global-skills/resend/references/receiving.md +29 -10
- package/dist/global-skills/resend/references/sending/email-management.md +14 -4
- package/dist/global-skills/resend/references/topics.md +9 -6
- package/dist/global-skills/resend/references/usage.md +117 -0
- package/dist/global-skills/resend/references/webhooks.md +59 -2
- package/dist/global-skills/stripe-best-practices/SKILL.md +35 -29
- package/dist/global-skills/stripe-best-practices/references/billing.md +9 -2
- package/dist/global-skills/stripe-best-practices/references/payments.md +4 -2
- package/dist/global-skills/stripe-best-practices/references/security.md +3 -1
- package/dist/global-skills/stripe-best-practices/references/tax.md +39 -20
- package/dist/global-skills/supabase/SKILL.md +6 -0
- package/dist/global-skills/use-railway/SKILL.md +42 -22
- package/dist/global-skills/use-railway/references/analyze-db.md +7 -6
- package/dist/global-skills/use-railway/references/cloud-agents.md +70 -0
- package/dist/global-skills/use-railway/references/configure.md +17 -2
- package/dist/global-skills/use-railway/references/databases.md +107 -0
- package/dist/global-skills/use-railway/references/deploy.md +5 -5
- package/dist/global-skills/use-railway/references/feature-flags.md +25 -13
- package/dist/global-skills/use-railway/references/iac.md +66 -77
- package/dist/global-skills/use-railway/references/operate.md +26 -3
- package/dist/global-skills/use-railway/references/request.md +31 -23
- package/dist/global-skills/use-railway/references/setup.md +16 -5
- package/dist/global-skills/use-railway/references/tracing.md +261 -0
- package/dist/global-skills/use-railway/references/usage.md +52 -0
- package/dist/global-skills/validate-my-idea/SKILL.md +54 -0
- package/dist/global-skills/{feedback → vybekiit-feedback}/SKILL.md +16 -12
- package/dist/global-skills/watch-my-app/SKILL.md +53 -0
- package/dist/global-skills/workers-best-practices/SKILL.md +36 -103
- package/dist/global-skills/workers-best-practices/references/configuration.md +139 -0
- package/dist/global-skills/workers-best-practices/references/platform-apis.md +51 -0
- package/dist/global-skills/workers-best-practices/references/{rules.md → runtime-patterns.md} +13 -137
- package/dist/global-skills/wrangler/SKILL.md +48 -901
- package/dist/global-skills/xcode-project-setup/scripts/xcode_spm_setup/Sources/main.swift +19 -15
- package/package.json +9 -8
- package/dist/global-skills/expo-native-ui/references/animations.md +0 -220
- package/dist/global-skills/firebase-firestore/references/enterprise/security_rules.md +0 -577
- package/dist/global-skills/workers-best-practices/references/review.md +0 -174
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
> AI agents: this is one page from PostHog's docs. Full index of Markdown docs for LLMs: https://posthog.com/llms.txt
|
|
2
2
|
|
|
3
|
-
# Python
|
|
4
|
-
|
|
5
|
-
Copy page
|
|
6
|
-
|
|
7
|
-
# Python - Docs
|
|
3
|
+
# Python
|
|
8
4
|
|
|
9
5
|
The Python SDK makes it easy to capture events, evaluate feature flags, track errors, and more in your Python apps.
|
|
10
6
|
|
|
@@ -14,8 +10,6 @@ The Python SDK makes it easy to capture events, evaluate feature flags, track er
|
|
|
14
10
|
|
|
15
11
|
Terminal
|
|
16
12
|
|
|
17
|
-
PostHog AI
|
|
18
|
-
|
|
19
13
|
```bash
|
|
20
14
|
pip install posthog
|
|
21
15
|
```
|
|
@@ -28,10 +22,9 @@ In your app, import the `posthog` library and set your project token and host **
|
|
|
28
22
|
|
|
29
23
|
Python
|
|
30
24
|
|
|
31
|
-
PostHog AI
|
|
32
|
-
|
|
33
25
|
```python
|
|
34
26
|
from posthog import Posthog
|
|
27
|
+
|
|
35
28
|
posthog = Posthog('<ph_project_token>', host='https://us.i.posthog.com')
|
|
36
29
|
```
|
|
37
30
|
|
|
@@ -39,6 +32,101 @@ posthog = Posthog('<ph_project_token>', host='https://us.i.posthog.com')
|
|
|
39
32
|
|
|
40
33
|
You can find your project token and instance address in the [project settings](https://app.posthog.com/project/settings) page in PostHog.
|
|
41
34
|
|
|
35
|
+
## Use the asyncio client
|
|
36
|
+
|
|
37
|
+
The Python SDK includes an asyncio-native client in version `7.45.0` and later. Continue to use `Posthog` in synchronous apps. For an asyncio app, install the optional async dependencies:
|
|
38
|
+
|
|
39
|
+
Terminal
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
pip install "posthog[async]>=7.45.0"
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Import `AsyncPosthog`, the customer-facing name for `AsyncClient`. Both names provide the same async context manager and lifecycle methods. Keep one client for the lifetime of your app. For example, use a FastAPI lifespan handler:
|
|
46
|
+
|
|
47
|
+
Python
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
import os
|
|
51
|
+
from contextlib import asynccontextmanager
|
|
52
|
+
|
|
53
|
+
from fastapi import FastAPI
|
|
54
|
+
from posthog import AsyncPosthog
|
|
55
|
+
|
|
56
|
+
@asynccontextmanager
|
|
57
|
+
async def lifespan(app: FastAPI):
|
|
58
|
+
async with AsyncPosthog(
|
|
59
|
+
os.environ["POSTHOG_PROJECT_TOKEN"],
|
|
60
|
+
host=os.environ["POSTHOG_HOST"],
|
|
61
|
+
secret_key=os.environ.get("POSTHOG_FEATURE_FLAGS_SECURE_API_KEY"),
|
|
62
|
+
) as posthog:
|
|
63
|
+
app.state.posthog = posthog
|
|
64
|
+
yield
|
|
65
|
+
|
|
66
|
+
app = FastAPI(lifespan=lifespan)
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Exiting the context calls `shutdown()`. This flushes buffered events, waits for in-flight operations, stops the workers, and closes the HTTP transport. If you don't use the context manager, call `await posthog.shutdown()` during app shutdown. `await posthog.join()` has the same effect.
|
|
70
|
+
|
|
71
|
+
### Capture events without blocking the event loop
|
|
72
|
+
|
|
73
|
+
`capture()` queues an event and returns without waiting for a network request. Don't await it:
|
|
74
|
+
|
|
75
|
+
Python
|
|
76
|
+
|
|
77
|
+
```python
|
|
78
|
+
posthog.capture(
|
|
79
|
+
"event_name",
|
|
80
|
+
distinct_id="user-distinct-id",
|
|
81
|
+
properties={"source": "fastapi"},
|
|
82
|
+
)
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Use `capture_immediate()` when your code must wait for that event's delivery attempt:
|
|
86
|
+
|
|
87
|
+
Python
|
|
88
|
+
|
|
89
|
+
```python
|
|
90
|
+
capture_id = await posthog.capture_immediate(
|
|
91
|
+
"event_name",
|
|
92
|
+
distinct_id="user-distinct-id",
|
|
93
|
+
)
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Evaluate feature flags
|
|
97
|
+
|
|
98
|
+
Await `evaluate_flags()` once, then use its snapshot with synchronous in-memory accessors. Pass the same snapshot to `capture()` to attach the exact values used for branching without another feature flag request:
|
|
99
|
+
|
|
100
|
+
Python
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
flags = await posthog.evaluate_flags("user-distinct-id")
|
|
104
|
+
|
|
105
|
+
if flags.is_enabled("new-checkout"):
|
|
106
|
+
# Show the new checkout
|
|
107
|
+
pass
|
|
108
|
+
|
|
109
|
+
posthog.capture(
|
|
110
|
+
"checkout started",
|
|
111
|
+
distinct_id="user-distinct-id",
|
|
112
|
+
flags=flags,
|
|
113
|
+
)
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
The snapshot provides synchronous `is_enabled()`, `get_flag()`, and `get_flag_payload()` accessors. The awaited `evaluate_flags()` call also accepts `groups`, `person_properties`, `group_properties`, `disable_geoip`, `flag_keys`, and `device_id` arguments.
|
|
117
|
+
|
|
118
|
+
### Fetch remote config
|
|
119
|
+
|
|
120
|
+
Initialize the client with a server-side [feature flags secure API key](/docs/feature-flags/remote-config.md#step-1-find-your-feature-flags-secure-api-key) as `secret_key`, then await the remote config request:
|
|
121
|
+
|
|
122
|
+
Python
|
|
123
|
+
|
|
124
|
+
```python
|
|
125
|
+
config = await posthog.get_remote_config_payload("landing-page-config")
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
See [Remote config](/docs/feature-flags/remote-config.md) for setup and security details.
|
|
129
|
+
|
|
42
130
|
## Identifying users
|
|
43
131
|
|
|
44
132
|
> **Identifying users is required.** Backend events need a `distinct_id` to associate events with the correct user.
|
|
@@ -47,10 +135,9 @@ You can find your project token and instance address in the [project settings](h
|
|
|
47
135
|
>
|
|
48
136
|
> Python
|
|
49
137
|
>
|
|
50
|
-
> PostHog AI
|
|
51
|
-
>
|
|
52
138
|
> ```python
|
|
53
139
|
> from posthog import new_context, identify_context, capture
|
|
140
|
+
>
|
|
54
141
|
> @app.get("/foo")
|
|
55
142
|
> def foo(current_user: User = Depends(get_current_user)):
|
|
56
143
|
> with new_context(): # Set context at the top of a route
|
|
@@ -67,12 +154,12 @@ You can send custom events using `capture`:
|
|
|
67
154
|
|
|
68
155
|
Python
|
|
69
156
|
|
|
70
|
-
PostHog AI
|
|
71
|
-
|
|
72
157
|
```python
|
|
73
158
|
# Events captured with no context or explicit distinct_id are marked as personless and have an auto-generated distinct_id:
|
|
74
159
|
posthog.capture('some-anon-event')
|
|
160
|
+
|
|
75
161
|
from posthog import identify_context, new_context
|
|
162
|
+
|
|
76
163
|
# Use contexts to manage user identification across multiple capture calls
|
|
77
164
|
with new_context():
|
|
78
165
|
identify_context('distinct_id_of_the_user')
|
|
@@ -92,8 +179,6 @@ Optionally, you can include additional information with the event by including a
|
|
|
92
179
|
|
|
93
180
|
Python
|
|
94
181
|
|
|
95
|
-
PostHog AI
|
|
96
|
-
|
|
97
182
|
```python
|
|
98
183
|
posthog.capture(
|
|
99
184
|
"user_signed_up",
|
|
@@ -111,20 +196,16 @@ If you're aiming for a backend-only implementation of PostHog and won't be captu
|
|
|
111
196
|
|
|
112
197
|
Python
|
|
113
198
|
|
|
114
|
-
PostHog AI
|
|
115
|
-
|
|
116
199
|
```python
|
|
117
200
|
posthog.capture('$pageview', distinct_id="distinct_id_of_the_user", properties={'$current_url': 'https://example.com'})
|
|
118
201
|
```
|
|
119
202
|
|
|
120
203
|
## Person profiles and properties
|
|
121
204
|
|
|
122
|
-
The Python SDK captures identified events if the current context is identified or if you pass a distinct ID explicitly. These create [person profiles](/docs/data/persons.md). To set [person properties](/docs/
|
|
205
|
+
The Python SDK captures identified events if the current context is identified or if you pass a distinct ID explicitly. These create [person profiles](/docs/data/persons.md). To set [person properties](/docs/product-analytics/person-properties.md) in these profiles, include them when capturing an event:
|
|
123
206
|
|
|
124
207
|
Python
|
|
125
208
|
|
|
126
|
-
PostHog AI
|
|
127
|
-
|
|
128
209
|
```python
|
|
129
210
|
# Passing a distinct id explicitly
|
|
130
211
|
posthog.capture(
|
|
@@ -135,6 +216,7 @@ posthog.capture(
|
|
|
135
216
|
'$set_once': {'initial_url': '/blog'}
|
|
136
217
|
}
|
|
137
218
|
)
|
|
219
|
+
|
|
138
220
|
# Using contexts
|
|
139
221
|
from posthog import new_context, identify_context
|
|
140
222
|
with new_context():
|
|
@@ -142,14 +224,12 @@ with new_context():
|
|
|
142
224
|
posthog.capture('event_name')
|
|
143
225
|
```
|
|
144
226
|
|
|
145
|
-
For more details on the difference between `$set` and `$set_once`, see our [person properties docs](/docs/
|
|
227
|
+
For more details on the difference between `$set` and `$set_once`, see our [person properties docs](/docs/product-analytics/person-properties.md#what-is-the-difference-between-set-and-set_once).
|
|
146
228
|
|
|
147
229
|
To capture [anonymous events](/docs/data/anonymous-vs-identified-events.md) without person profiles, set the event's `$process_person_profile` property to `False`. Events captured with no context or explicit distinct\_id are marked as personless, and will have an auto-generated distinct\_id:
|
|
148
230
|
|
|
149
231
|
Python
|
|
150
232
|
|
|
151
|
-
PostHog AI
|
|
152
|
-
|
|
153
233
|
```python
|
|
154
234
|
posthog.capture(
|
|
155
235
|
event='event_name',
|
|
@@ -167,8 +247,6 @@ In this case, you can use `alias` to assign another distinct ID to the same user
|
|
|
167
247
|
|
|
168
248
|
Python
|
|
169
249
|
|
|
170
|
-
PostHog AI
|
|
171
|
-
|
|
172
250
|
```python
|
|
173
251
|
posthog.alias(previous_id='distinct_id', distinct_id='alias_id')
|
|
174
252
|
```
|
|
@@ -185,18 +263,20 @@ You can enter a context using the `with` statement:
|
|
|
185
263
|
|
|
186
264
|
Python
|
|
187
265
|
|
|
188
|
-
PostHog AI
|
|
189
|
-
|
|
190
266
|
```python
|
|
191
267
|
from posthog import new_context, tag, set_context_session, identify_context
|
|
268
|
+
|
|
192
269
|
with new_context():
|
|
193
270
|
tag("transaction_id", "abc123")
|
|
194
271
|
tag("some_arbitrary_value", {"tags": "can be dicts"})
|
|
272
|
+
|
|
195
273
|
# Sessions are UUIDv7 values and used to track a sequence of events that occur within a single user session
|
|
196
274
|
# See https://posthog.com/docs/data/sessions
|
|
197
275
|
set_context_session(session_id)
|
|
276
|
+
|
|
198
277
|
# Setting the context-level distinct ID. See below for more details.
|
|
199
278
|
identify_context(user_id)
|
|
279
|
+
|
|
200
280
|
# This event is captured with the distinct ID, session ID, and tags set above
|
|
201
281
|
posthog.capture("order_processed")
|
|
202
282
|
```
|
|
@@ -205,13 +285,14 @@ Contexts are persisted across function calls. If you enter one and then call a f
|
|
|
205
285
|
|
|
206
286
|
Python
|
|
207
287
|
|
|
208
|
-
PostHog AI
|
|
209
|
-
|
|
210
288
|
```python
|
|
211
289
|
from posthog import new_context, tag
|
|
290
|
+
|
|
212
291
|
def some_function():
|
|
213
292
|
# When called from `outer_function`, this event is captured with the property some-key="value-4"
|
|
214
293
|
posthog.capture("order_processed")
|
|
294
|
+
|
|
295
|
+
|
|
215
296
|
def outer_function():
|
|
216
297
|
with new_context():
|
|
217
298
|
tag("some-key", "value-4")
|
|
@@ -222,10 +303,9 @@ Contexts are nested, so tags added to a parent context are inherited by child co
|
|
|
222
303
|
|
|
223
304
|
Python
|
|
224
305
|
|
|
225
|
-
PostHog AI
|
|
226
|
-
|
|
227
306
|
```python
|
|
228
307
|
from posthog import new_context, tag
|
|
308
|
+
|
|
229
309
|
with new_context():
|
|
230
310
|
tag("some-key", "value-1")
|
|
231
311
|
tag("some-other-key", "another-value")
|
|
@@ -233,6 +313,7 @@ with new_context():
|
|
|
233
313
|
tag("some-key", "value-2")
|
|
234
314
|
# This event is captured with some-key="value-2" and some-other-key="another-value"
|
|
235
315
|
posthog.capture("order_processed")
|
|
316
|
+
|
|
236
317
|
# This event is captured with some-key="value-1" and some-other-key="another-value"
|
|
237
318
|
posthog.capture("order_processed")
|
|
238
319
|
```
|
|
@@ -241,10 +322,9 @@ You can disable this nesting behavior by passing `fresh=True` to `new_context`:
|
|
|
241
322
|
|
|
242
323
|
Python
|
|
243
324
|
|
|
244
|
-
PostHog AI
|
|
245
|
-
|
|
246
325
|
```python
|
|
247
326
|
from posthog import new_context, tag
|
|
327
|
+
|
|
248
328
|
with new_context(fresh=True):
|
|
249
329
|
tag("some-key", "value-2")
|
|
250
330
|
# This event only has the property some-key="value-2" from the fresh context
|
|
@@ -259,10 +339,9 @@ Contexts can be associated with a distinct ID by calling `posthog.identify_conte
|
|
|
259
339
|
|
|
260
340
|
Python
|
|
261
341
|
|
|
262
|
-
PostHog AI
|
|
263
|
-
|
|
264
342
|
```python
|
|
265
343
|
from posthog import identify_context
|
|
344
|
+
|
|
266
345
|
identify_context("distinct-id")
|
|
267
346
|
```
|
|
268
347
|
|
|
@@ -270,10 +349,9 @@ Within a context associated with a distinct ID, all events captured are associat
|
|
|
270
349
|
|
|
271
350
|
Python
|
|
272
351
|
|
|
273
|
-
PostHog AI
|
|
274
|
-
|
|
275
352
|
```python
|
|
276
353
|
from posthog import new_context, identify_context
|
|
354
|
+
|
|
277
355
|
with new_context():
|
|
278
356
|
identify_context("distinct-id")
|
|
279
357
|
posthog.capture("order_processed") # will be associated with distinct-id
|
|
@@ -290,8 +368,6 @@ Contexts can be associated with a session ID by calling `posthog.set_context_ses
|
|
|
290
368
|
|
|
291
369
|
Python
|
|
292
370
|
|
|
293
|
-
PostHog AI
|
|
294
|
-
|
|
295
371
|
```python
|
|
296
372
|
from posthog import new_context, set_context_session
|
|
297
373
|
with new_context():
|
|
@@ -317,13 +393,13 @@ By default exceptions raised within a context are captured and available in the
|
|
|
317
393
|
|
|
318
394
|
Python
|
|
319
395
|
|
|
320
|
-
PostHog AI
|
|
321
|
-
|
|
322
396
|
```python
|
|
323
397
|
from posthog import new_context, tag
|
|
398
|
+
|
|
324
399
|
with new_context(capture_exceptions=False):
|
|
325
400
|
tag("transaction_id", "abc123")
|
|
326
401
|
tag("some_arbitrary_value", {"tags": "can be dicts"})
|
|
402
|
+
|
|
327
403
|
# This event will be captured with the tags set above
|
|
328
404
|
posthog.capture("order_processed")
|
|
329
405
|
# This exception will not be captured
|
|
@@ -336,10 +412,9 @@ The SDK exposes a function decorator. It takes the same `fresh` and `capture_exc
|
|
|
336
412
|
|
|
337
413
|
Python
|
|
338
414
|
|
|
339
|
-
PostHog AI
|
|
340
|
-
|
|
341
415
|
```python
|
|
342
416
|
from posthog import scoped, identify_context
|
|
417
|
+
|
|
343
418
|
@scoped(fresh=True)
|
|
344
419
|
def process_order(user, order_id):
|
|
345
420
|
identify_context(user.distinct_id)
|
|
@@ -357,8 +432,6 @@ To capture an event and associate it with a group:
|
|
|
357
432
|
|
|
358
433
|
Python
|
|
359
434
|
|
|
360
|
-
PostHog AI
|
|
361
|
-
|
|
362
435
|
```python
|
|
363
436
|
posthog.capture('some_event', groups={'company': 'company_id_in_your_db'})
|
|
364
437
|
```
|
|
@@ -367,8 +440,6 @@ To update properties on a group:
|
|
|
367
440
|
|
|
368
441
|
Python
|
|
369
442
|
|
|
370
|
-
PostHog AI
|
|
371
|
-
|
|
372
443
|
```python
|
|
373
444
|
posthog.group_identify('company', 'company_id_in_your_db', {
|
|
374
445
|
'name': 'Awesome Inc.',
|
|
@@ -380,6 +451,8 @@ The `name` is a special property which is used in the PostHog UI for the name of
|
|
|
380
451
|
|
|
381
452
|
## Feature flags
|
|
382
453
|
|
|
454
|
+
The examples in this section use the synchronous `Posthog` client. For `AsyncPosthog`, use the [awaited feature flag example](#evaluate-feature-flags). The returned snapshot uses the same accessors.
|
|
455
|
+
|
|
383
456
|
PostHog's [feature flags](/docs/feature-flags.md) enable you to safely deploy and roll back new features as well as target specific users and groups with them.
|
|
384
457
|
|
|
385
458
|
There are two steps to implement feature flags in Python:
|
|
@@ -392,10 +465,9 @@ Call `posthog.evaluate_flags()` once for the user, then read values from the ret
|
|
|
392
465
|
|
|
393
466
|
Python
|
|
394
467
|
|
|
395
|
-
PostHog AI
|
|
396
|
-
|
|
397
468
|
```python
|
|
398
469
|
flags = posthog.evaluate_flags("distinct_id_of_your_user")
|
|
470
|
+
|
|
399
471
|
if flags.is_enabled("flag-key"):
|
|
400
472
|
# Do something differently for this user
|
|
401
473
|
# Optional: fetch the payload
|
|
@@ -406,11 +478,11 @@ if flags.is_enabled("flag-key"):
|
|
|
406
478
|
|
|
407
479
|
Python
|
|
408
480
|
|
|
409
|
-
PostHog AI
|
|
410
|
-
|
|
411
481
|
```python
|
|
412
482
|
flags = posthog.evaluate_flags("distinct_id_of_your_user")
|
|
483
|
+
|
|
413
484
|
enabled_variant = flags.get_flag("flag-key")
|
|
485
|
+
|
|
414
486
|
if enabled_variant == "variant-key": # replace "variant-key" with the key of your variant
|
|
415
487
|
# Do something differently for this user
|
|
416
488
|
# Optional: fetch the payload
|
|
@@ -435,13 +507,13 @@ Pass the same `flags` object that you used for branching. This attaches the exac
|
|
|
435
507
|
|
|
436
508
|
Python
|
|
437
509
|
|
|
438
|
-
PostHog AI
|
|
439
|
-
|
|
440
510
|
```python
|
|
441
511
|
flags = posthog.evaluate_flags("distinct_id_of_your_user")
|
|
512
|
+
|
|
442
513
|
if flags.is_enabled("flag-key"):
|
|
443
514
|
# Do something differently for this user
|
|
444
515
|
pass
|
|
516
|
+
|
|
445
517
|
posthog.capture(
|
|
446
518
|
"event_name",
|
|
447
519
|
distinct_id="distinct_id_of_your_user",
|
|
@@ -455,8 +527,6 @@ To reduce event property bloat, pass a filtered snapshot:
|
|
|
455
527
|
|
|
456
528
|
Python
|
|
457
529
|
|
|
458
|
-
PostHog AI
|
|
459
|
-
|
|
460
530
|
```python
|
|
461
531
|
# Attach only flags accessed with is_enabled() or get_flag() before this call
|
|
462
532
|
posthog.capture(
|
|
@@ -464,6 +534,7 @@ posthog.capture(
|
|
|
464
534
|
distinct_id="distinct_id_of_your_user",
|
|
465
535
|
flags=flags.only_accessed(),
|
|
466
536
|
)
|
|
537
|
+
|
|
467
538
|
# Attach only specific flags
|
|
468
539
|
posthog.capture(
|
|
469
540
|
"event_name",
|
|
@@ -480,8 +551,6 @@ In the event properties, include `$feature/feature_flag_name: variant_key`:
|
|
|
480
551
|
|
|
481
552
|
Python
|
|
482
553
|
|
|
483
|
-
PostHog AI
|
|
484
|
-
|
|
485
554
|
```python
|
|
486
555
|
posthog.capture(
|
|
487
556
|
"event_name",
|
|
@@ -499,8 +568,6 @@ By default, `posthog.evaluate_flags()` evaluates every flag for the user. If you
|
|
|
499
568
|
|
|
500
569
|
Python
|
|
501
570
|
|
|
502
|
-
PostHog AI
|
|
503
|
-
|
|
504
571
|
```python
|
|
505
572
|
flags = posthog.evaluate_flags(
|
|
506
573
|
"distinct_id_of_your_user",
|
|
@@ -526,8 +593,6 @@ For example:
|
|
|
526
593
|
|
|
527
594
|
Python
|
|
528
595
|
|
|
529
|
-
PostHog AI
|
|
530
|
-
|
|
531
596
|
```python
|
|
532
597
|
flags = posthog.evaluate_flags(
|
|
533
598
|
"distinct_id_of_the_user",
|
|
@@ -541,6 +606,7 @@ flags = posthog.evaluate_flags(
|
|
|
541
606
|
"another_group_type": {"group_property_name": "value"},
|
|
542
607
|
},
|
|
543
608
|
)
|
|
609
|
+
|
|
544
610
|
if flags.is_enabled("flag-key"):
|
|
545
611
|
# Do something differently for this user
|
|
546
612
|
```
|
|
@@ -578,8 +644,6 @@ You can configure the `feature_flags_request_timeout_seconds` parameter when ini
|
|
|
578
644
|
|
|
579
645
|
Python
|
|
580
646
|
|
|
581
|
-
PostHog AI
|
|
582
|
-
|
|
583
647
|
```python
|
|
584
648
|
posthog = Posthog(
|
|
585
649
|
"<ph_project_token>",
|
|
@@ -602,19 +666,20 @@ In multi-worker or edge environments, you can implement custom caching for flag
|
|
|
602
666
|
|
|
603
667
|
## Experiments (A/B tests)
|
|
604
668
|
|
|
605
|
-
Since [experiments](/docs/experiments/start-here.md) use feature flags, the code for running an experiment is very similar to the feature flags code:
|
|
669
|
+
Since [experiments](/docs/experiments/start-here.md) use feature flags, the code for running an experiment is very similar to the feature flags code. This example uses the synchronous `Posthog` client:
|
|
606
670
|
|
|
607
671
|
Python
|
|
608
672
|
|
|
609
|
-
PostHog AI
|
|
610
|
-
|
|
611
673
|
```python
|
|
612
674
|
flags = posthog.evaluate_flags("user_distinct_id")
|
|
613
675
|
variant = flags.get_flag("experiment-feature-flag-key")
|
|
676
|
+
|
|
614
677
|
if variant == "variant-name":
|
|
615
678
|
# Do something
|
|
616
679
|
```
|
|
617
680
|
|
|
681
|
+
With `AsyncPosthog`, await the evaluation: `flags = await posthog.evaluate_flags("user_distinct_id")`. The remaining snapshot access is the same.
|
|
682
|
+
|
|
618
683
|
It's also possible to [run experiments without using feature flags](/docs/experiments/running-experiments-without-feature-flags.md).
|
|
619
684
|
|
|
620
685
|
## AI Observability
|
|
@@ -627,10 +692,9 @@ You can [autocapture exceptions](/docs/error-tracking/installation.md) by settin
|
|
|
627
692
|
|
|
628
693
|
Python
|
|
629
694
|
|
|
630
|
-
PostHog AI
|
|
631
|
-
|
|
632
695
|
```python
|
|
633
696
|
from posthog import Posthog
|
|
697
|
+
|
|
634
698
|
posthog = Posthog("<ph_project_token>", enable_exception_autocapture=True, ...)
|
|
635
699
|
```
|
|
636
700
|
|
|
@@ -638,8 +702,6 @@ You can also manually capture exceptions using the `capture_exception` method:
|
|
|
638
702
|
|
|
639
703
|
Python
|
|
640
704
|
|
|
641
|
-
PostHog AI
|
|
642
|
-
|
|
643
705
|
```python
|
|
644
706
|
posthog.capture_exception(e, distinct_id='user_distinct_id', properties=additional_properties)
|
|
645
707
|
```
|
|
@@ -652,8 +714,6 @@ The Python SDK can automatically capture the state of local variables when an ex
|
|
|
652
714
|
|
|
653
715
|
Python
|
|
654
716
|
|
|
655
|
-
PostHog AI
|
|
656
|
-
|
|
657
717
|
```python
|
|
658
718
|
posthog = Posthog(
|
|
659
719
|
"<ph_project_token>",
|
|
@@ -664,6 +724,246 @@ posthog = Posthog(
|
|
|
664
724
|
|
|
665
725
|
You can configure which variables are captured, masked, or ignored. See the [code variables documentation](/docs/error-tracking/code-variables/python.md) for detailed configuration options.
|
|
666
726
|
|
|
727
|
+
## Distributed tracing
|
|
728
|
+
|
|
729
|
+
> Requires `posthog` version 7.58.0 or later.
|
|
730
|
+
|
|
731
|
+
**The span API is experimental**
|
|
732
|
+
|
|
733
|
+
Tracing is new in the Python SDK and its API can still change in a minor release. Spans you send are kept – it's the SDK surface that isn't frozen yet.
|
|
734
|
+
|
|
735
|
+
Tracing records **spans** – timed units of work – so you can see where time went in a request and how work fans out across your services. Spans created inside a [context](#contexts) automatically carry the person and session they belong to, so a slow trace links back to the person who experienced it.
|
|
736
|
+
|
|
737
|
+
Tracing is off until you set the `traces` option. No OpenTelemetry dependency is required. For what you can do with spans once they arrive, see [Distributed tracing](/docs/distributed-tracing/start-here.md).
|
|
738
|
+
|
|
739
|
+
Python
|
|
740
|
+
|
|
741
|
+
```python
|
|
742
|
+
from posthog import Posthog
|
|
743
|
+
|
|
744
|
+
posthog = Posthog(
|
|
745
|
+
"<ph_project_token>",
|
|
746
|
+
host="https://us.i.posthog.com",
|
|
747
|
+
traces={"service_name": "checkout-api"},
|
|
748
|
+
)
|
|
749
|
+
```
|
|
750
|
+
|
|
751
|
+
If you use the module-level API instead, set `posthog.traces = {"service_name": "checkout-api"}` alongside your other options, before you start the first span.
|
|
752
|
+
|
|
753
|
+
Set `service_name` – PostHog groups operations by service and span name.
|
|
754
|
+
|
|
755
|
+
Tracing is available on the synchronous `Posthog` client and the module-level API. `AsyncPosthog` doesn't support it yet.
|
|
756
|
+
|
|
757
|
+
### Creating spans
|
|
758
|
+
|
|
759
|
+
`start_span` returns a span. Use it in a `with` block to make it the active span for the block and end it when the block exits. Spans started inside the block nest underneath it automatically.
|
|
760
|
+
|
|
761
|
+
Python
|
|
762
|
+
|
|
763
|
+
```python
|
|
764
|
+
with posthog.start_span("POST /checkout", kind="server") as span:
|
|
765
|
+
span.set_attribute("plan", user.plan)
|
|
766
|
+
|
|
767
|
+
with posthog.start_span("create-order"):
|
|
768
|
+
order = create_order(cart)
|
|
769
|
+
with posthog.start_span("charge-card"):
|
|
770
|
+
stripe.charge(order)
|
|
771
|
+
```
|
|
772
|
+
|
|
773
|
+
If an exception escapes the block, the span records it, its status is set to `error`, and the exception propagates unchanged. `KeyboardInterrupt`, `GeneratorExit`, and `asyncio.CancelledError` still end the span but aren't recorded as failures.
|
|
774
|
+
|
|
775
|
+
The recorded exception includes the stack trace, which contains file paths from your server. If you'd rather those didn't leave your process, remove `exception.stacktrace` in [`before_span_send`](#scrubbing-and-dropping-spans).
|
|
776
|
+
|
|
777
|
+
For work that can't wrap a block, call `start_span` without `with`. **A span started this way isn't active**, so spans started afterwards aren't its children unless you pass `parent` explicitly – and you must call `end()` yourself.
|
|
778
|
+
|
|
779
|
+
Python
|
|
780
|
+
|
|
781
|
+
```python
|
|
782
|
+
span = posthog.start_span("background-sync", attributes={"queue": "emails"})
|
|
783
|
+
try:
|
|
784
|
+
# Explicitly parent a child to a span that isn't active.
|
|
785
|
+
child = posthog.start_span("send-batch", parent=span)
|
|
786
|
+
child.end()
|
|
787
|
+
finally:
|
|
788
|
+
span.end()
|
|
789
|
+
```
|
|
790
|
+
|
|
791
|
+
`get_active_span()` returns the span active in the current context, or `None` when there isn't one.
|
|
792
|
+
|
|
793
|
+
The active span is tracked with [`contextvars`](https://docs.python.org/3/library/contextvars.html), so it carries across `await` in asyncio code. Threads don't reliably inherit it – pass `parent=span` to continue the trace in a thread you start or a `ThreadPoolExecutor` task. A forked child process starts with no active span, so pass `parent` there too.
|
|
794
|
+
|
|
795
|
+
`start_span` always returns a usable span, even when tracing is off, so your code never needs to check whether tracing is enabled.
|
|
796
|
+
|
|
797
|
+
### Span names and attributes
|
|
798
|
+
|
|
799
|
+
Span names should be low-cardinality operation names – `GET /users/:id`, not `GET /users/123`. Variable values belong in attributes. Strings, booleans, integers, and floats keep their type. Lists and dictionaries are sent as arrays and maps, but PostHog stores them as serialized strings. Setting an attribute to `None` removes it, and any other value is converted to a string.
|
|
800
|
+
|
|
801
|
+
Python
|
|
802
|
+
|
|
803
|
+
```python
|
|
804
|
+
with posthog.start_span("GET /users/:id", kind="server") as span:
|
|
805
|
+
span.set_attributes({"user.id": user_id, "db.rows": len(rows)})
|
|
806
|
+
span.add_event("cache-miss")
|
|
807
|
+
|
|
808
|
+
if not rows:
|
|
809
|
+
span.set_status("error", "user not found")
|
|
810
|
+
```
|
|
811
|
+
|
|
812
|
+
| Method | Description |
|
|
813
|
+
| --- | --- |
|
|
814
|
+
| `set_attribute(key, value)` | Set a single attribute |
|
|
815
|
+
| `set_attributes(attributes)` | Merge several attributes at once |
|
|
816
|
+
| `add_event(name, attributes=None, timestamp=None)` | Record a timestamped event within the span |
|
|
817
|
+
| `set_status(code, message=None)` | Set the outcome: `"ok"` or `"error"`. `"ok"` is final – an exception raised later in the `with` block doesn't override it |
|
|
818
|
+
| `record_exception(exception)` | Attach an exception event carrying the type, message, and – for a raised exception – stack trace, and set status to `error`. Use it for exceptions you catch and handle |
|
|
819
|
+
| `update_name(name)` | Replace the span name, e.g. once a route template resolves |
|
|
820
|
+
| `traceparent()` | This span's W3C `traceparent` header value, or `None` |
|
|
821
|
+
| `tracestate()` | This span's W3C `tracestate` value, or `None` when it has none |
|
|
822
|
+
| `end(end_time=None)` | End the span and queue it for export. A `with` block does this for you |
|
|
823
|
+
|
|
824
|
+
Every method except `traceparent()`, `tracestate()`, and `end()` returns the span, so calls chain. Calls after `end()` are ignored.
|
|
825
|
+
|
|
826
|
+
`start_span` takes these keyword arguments:
|
|
827
|
+
|
|
828
|
+
| Argument | Description |
|
|
829
|
+
| --- | --- |
|
|
830
|
+
| `kind` | What the work is: `"internal"` (default), `"server"` for an inbound request, `"client"` for an outbound call, `"producer"` or `"consumer"` for queue work |
|
|
831
|
+
| `attributes` | Attributes to set at span start |
|
|
832
|
+
| `parent` | A span, or an inbound W3C `traceparent` string to continue a trace another service started. Defaults to the active span |
|
|
833
|
+
| `tracestate` | The W3C `tracestate` accompanying a `traceparent` string. Ignored when `parent` is a span, which inherits its parent's |
|
|
834
|
+
| `start_time` | Backdate the span's start, as a `datetime` or seconds since the epoch. The server clamps a start more than 24 hours old to receive time; with `debug` on, the SDK prints a debug message when you pass one |
|
|
835
|
+
|
|
836
|
+
### Tracing across services
|
|
837
|
+
|
|
838
|
+
Spans use [W3C Trace Context](https://www.w3.org/TR/trace-context/), so a trace can span several services. Pass an inbound `traceparent` header as `parent` to continue a trace another service started, and send `span.traceparent()` onward when you call out.
|
|
839
|
+
|
|
840
|
+
app.py
|
|
841
|
+
|
|
842
|
+
```python
|
|
843
|
+
import requests
|
|
844
|
+
from flask import request
|
|
845
|
+
|
|
846
|
+
|
|
847
|
+
@app.post("/checkout")
|
|
848
|
+
def checkout():
|
|
849
|
+
with posthog.start_span(
|
|
850
|
+
"POST /checkout",
|
|
851
|
+
kind="server",
|
|
852
|
+
parent=request.headers.get("traceparent"),
|
|
853
|
+
) as span:
|
|
854
|
+
traceparent = span.traceparent()
|
|
855
|
+
|
|
856
|
+
requests.post(
|
|
857
|
+
"https://payments.internal/charge",
|
|
858
|
+
headers={"traceparent": traceparent} if traceparent else {},
|
|
859
|
+
)
|
|
860
|
+
|
|
861
|
+
return {"status": "ok"}
|
|
862
|
+
```
|
|
863
|
+
|
|
864
|
+
A malformed `traceparent` starts a new trace rather than raising. A missing one (`None`) falls back to the active span, if there is one.
|
|
865
|
+
|
|
866
|
+
A continued trace propagates the sampled flag it was handed, so a downstream sampler sees the decision the head service made. PostHog itself doesn't sample – a span is recorded and exported whichever way that flag is set.
|
|
867
|
+
|
|
868
|
+
### Linking traces to people and sessions
|
|
869
|
+
|
|
870
|
+
Spans created inside a [context](#contexts) that has a distinct ID or session ID automatically carry `posthogDistinctId` and `sessionId` attributes, which is what makes a trace reachable from a person or a Session Replay recording. In Django, the [contexts middleware](/docs/libraries/django.md#django-contexts-middleware) sets these for every request. Elsewhere, set them yourself:
|
|
871
|
+
|
|
872
|
+
Python
|
|
873
|
+
|
|
874
|
+
```python
|
|
875
|
+
from posthog import new_context, identify_context, set_context_session
|
|
876
|
+
|
|
877
|
+
with new_context():
|
|
878
|
+
identify_context(user.id)
|
|
879
|
+
set_context_session(session_id)
|
|
880
|
+
|
|
881
|
+
with posthog.start_span("POST /checkout"):
|
|
882
|
+
process_order()
|
|
883
|
+
```
|
|
884
|
+
|
|
885
|
+
Spans created outside a context with those values omit the attributes.
|
|
886
|
+
|
|
887
|
+
### Scrubbing and dropping spans
|
|
888
|
+
|
|
889
|
+
`before_span_send` runs on every finished span before it's queued for export. It receives the span as a dict with `name`, `kind`, `status`, `attributes`, `events`, `start_time_ns`, `end_time_ns`, `trace_id`, `span_id`, and `parent_span_id`. Edit it and return it, or return `None` to drop the span entirely.
|
|
890
|
+
|
|
891
|
+
Python
|
|
892
|
+
|
|
893
|
+
```python
|
|
894
|
+
def scrub_spans(span):
|
|
895
|
+
if span["attributes"].get("http.route") == "/health":
|
|
896
|
+
return None
|
|
897
|
+
|
|
898
|
+
span["attributes"].pop("http.request.header.authorization", None)
|
|
899
|
+
return span
|
|
900
|
+
|
|
901
|
+
|
|
902
|
+
posthog = Posthog(
|
|
903
|
+
"<ph_project_token>",
|
|
904
|
+
host="https://us.i.posthog.com",
|
|
905
|
+
traces={
|
|
906
|
+
"service_name": "checkout-api",
|
|
907
|
+
"before_span_send": scrub_spans,
|
|
908
|
+
},
|
|
909
|
+
)
|
|
910
|
+
```
|
|
911
|
+
|
|
912
|
+
Attributes are plain Python values, not the OTLP wire encoding. The hook runs after PostHog attaches `posthogDistinctId` and `sessionId`, so those are visible to the hook and can be scrubbed too. An exception's stack trace is on its event, under `event["attributes"]["exception.stacktrace"]`.
|
|
913
|
+
|
|
914
|
+
- `trace_id`, `span_id`, and `parent_span_id` are read-only. Rewriting them would orphan child spans that have already been exported, so changes are reverted.
|
|
915
|
+
- A hook that raises drops the span rather than exporting it without scrubbing.
|
|
916
|
+
- Pass a list to run several hooks in order. The first one to return `None` stops the chain.
|
|
917
|
+
- The hook must be a regular function. An `async` hook drops every span.
|
|
918
|
+
- If an entry isn't callable, tracing turns off for the client rather than exporting spans the hook was meant to scrub.
|
|
919
|
+
|
|
920
|
+
### Span limits
|
|
921
|
+
|
|
922
|
+
A span is capped at 128 attributes and 128 events, each event at 128 attributes, and each string attribute value at 8192 characters. The endpoint rejects a span that's too large, and a rejected span is lost whole rather than truncated, so the caps bound a span before it gets there.
|
|
923
|
+
|
|
924
|
+
Past the cap, the earliest attributes and events are kept and the number dropped is reported alongside the span, so a truncated span reads as truncated rather than as quietly incomplete. The attributes PostHog attaches itself – `posthogDistinctId` and `sessionId` – don't count toward the cap and are never dropped, so a span at the limit still links back to its person and session.
|
|
925
|
+
|
|
926
|
+
The event cap is absolute: an `exception` event the SDK records for you spends an ordinary slot like any other. A span that fills its events and then raises keeps its `error` status but not the exception detail, and reports the loss as a dropped event. Raise `max_events_per_span` on spans that record many events and can also fail.
|
|
927
|
+
|
|
928
|
+
The length bound reaches inside a value, including strings nested in lists and dictionaries, and applies to `exception.stacktrace` like any other attribute – a long stack trace keeps its last 8192 characters. All four caps are re-applied after `before_span_send`, so a hook that enriches a span can't push it back over.
|
|
929
|
+
|
|
930
|
+
### Configuration
|
|
931
|
+
|
|
932
|
+
| Option | Default | Description |
|
|
933
|
+
| --- | --- | --- |
|
|
934
|
+
| `service_name` | – | Name of the service producing spans. Set this |
|
|
935
|
+
| `service_version` | – | Version of the service |
|
|
936
|
+
| `environment` | – | Deployment environment, e.g. `production` |
|
|
937
|
+
| `resource_attributes` | – | Extra OTLP resource attributes. Takes precedence over the fields above |
|
|
938
|
+
| `flush_interval` | `5` | Seconds between exports of queued spans |
|
|
939
|
+
| `max_export_batch_size` | `512` | Maximum spans per request |
|
|
940
|
+
| `max_queue_size` | `2048` | Maximum spans held in memory. Spans beyond this are dropped |
|
|
941
|
+
| `max_live_spans` | `10000` | Maximum spans open at once. At the limit `start_span` returns a span that isn't recorded |
|
|
942
|
+
| `max_span_age` | `3600` | Once `max_live_spans` is reached, spans open longer than this many seconds are treated as leaked and never exported |
|
|
943
|
+
| `before_span_send` | – | Edit or drop each finished span before export. Return `None` to drop it |
|
|
944
|
+
| `max_attributes_per_span` | `128` | Maximum attributes you set on one span |
|
|
945
|
+
| `max_events_per_span` | `128` | Maximum events on one span |
|
|
946
|
+
| `max_attribute_value_length` | `8192` | Maximum characters in a string attribute value |
|
|
947
|
+
|
|
948
|
+
An invalid value falls back to its default with a warning. The exception is `before_span_send`: an entry that isn't callable turns tracing off.
|
|
949
|
+
|
|
950
|
+
### Shutdown and short-lived processes
|
|
951
|
+
|
|
952
|
+
Spans are exported on a background interval, even with `sync_mode` on. Both `flush()` and `shutdown()` export spans that have already ended. A span still open at `flush()` is exported once it ends; a span still open at `shutdown()` is discarded with a warning, so end your spans before shutting down – a `with` block does this for you. `shutdown()` gives queued spans up to 30 seconds to send.
|
|
953
|
+
|
|
954
|
+
In a serverless handler, call `flush()` before returning. Events and spans are flushed concurrently, so it costs one round trip, not two.
|
|
955
|
+
|
|
956
|
+
Python
|
|
957
|
+
|
|
958
|
+
```python
|
|
959
|
+
def handler(event, context):
|
|
960
|
+
with posthog.start_span("handler"):
|
|
961
|
+
do_work()
|
|
962
|
+
posthog.flush()
|
|
963
|
+
```
|
|
964
|
+
|
|
965
|
+
A script that exits without calling `shutdown()` still gets a brief best-effort flush at exit, but don't rely on it for spans you need.
|
|
966
|
+
|
|
667
967
|
## GeoIP properties
|
|
668
968
|
|
|
669
969
|
Before posthog-python v3.0, we added GeoIP properties to all incoming events by default. We also used these properties for feature flag evaluation, based on the IP address of the request. This isn't ideal since they are created based on your server IP address, rather than the user's, leading to incorrect location resolution.
|
|
@@ -674,8 +974,6 @@ You can go back to previous behavior by doing setting the `disable_geoip` argume
|
|
|
674
974
|
|
|
675
975
|
Python
|
|
676
976
|
|
|
677
|
-
PostHog AI
|
|
678
|
-
|
|
679
977
|
```python
|
|
680
978
|
posthog = Posthog('api_key', disable_geoip=False)
|
|
681
979
|
```
|
|
@@ -694,8 +992,6 @@ You can also explicitly chose to enable or disable GeoIP for a single capture re
|
|
|
694
992
|
|
|
695
993
|
Python
|
|
696
994
|
|
|
697
|
-
PostHog AI
|
|
698
|
-
|
|
699
995
|
```python
|
|
700
996
|
posthog.capture('test_event', disable_geoip=True|False)
|
|
701
997
|
```
|
|
@@ -708,8 +1004,6 @@ You can enable debug mode by setting the `debug` option to `True` in the `PostHo
|
|
|
708
1004
|
|
|
709
1005
|
Python
|
|
710
1006
|
|
|
711
|
-
PostHog AI
|
|
712
|
-
|
|
713
1007
|
```python
|
|
714
1008
|
posthog.debug = True
|
|
715
1009
|
```
|
|
@@ -720,8 +1014,6 @@ You can disable requests during tests by setting the `disabled` option to `True`
|
|
|
720
1014
|
|
|
721
1015
|
Python
|
|
722
1016
|
|
|
723
|
-
PostHog AI
|
|
724
|
-
|
|
725
1017
|
```python
|
|
726
1018
|
if settings.TEST:
|
|
727
1019
|
posthog.disabled = True
|
|
@@ -739,10 +1031,9 @@ TCP keepalive probes help prevent idle connections from being dropped by network
|
|
|
739
1031
|
|
|
740
1032
|
Python
|
|
741
1033
|
|
|
742
|
-
PostHog AI
|
|
743
|
-
|
|
744
1034
|
```python
|
|
745
1035
|
import posthog
|
|
1036
|
+
|
|
746
1037
|
posthog.enable_keep_alive()
|
|
747
1038
|
```
|
|
748
1039
|
|
|
@@ -754,10 +1045,9 @@ If you need each request to use a fresh connection, you can disable connection r
|
|
|
754
1045
|
|
|
755
1046
|
Python
|
|
756
1047
|
|
|
757
|
-
PostHog AI
|
|
758
|
-
|
|
759
1048
|
```python
|
|
760
1049
|
import posthog
|
|
1050
|
+
|
|
761
1051
|
posthog.disable_connection_reuse()
|
|
762
1052
|
```
|
|
763
1053
|
|
|
@@ -767,11 +1057,10 @@ For advanced use cases, you can configure arbitrary socket options on the underl
|
|
|
767
1057
|
|
|
768
1058
|
Python
|
|
769
1059
|
|
|
770
|
-
PostHog AI
|
|
771
|
-
|
|
772
1060
|
```python
|
|
773
1061
|
import socket
|
|
774
1062
|
import posthog
|
|
1063
|
+
|
|
775
1064
|
posthog.set_socket_options([
|
|
776
1065
|
(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1),
|
|
777
1066
|
# Add additional socket options as needed
|
|
@@ -786,19 +1075,25 @@ Use `before_send` to modify or drop events before they are queued for delivery.
|
|
|
786
1075
|
|
|
787
1076
|
Python
|
|
788
1077
|
|
|
789
|
-
PostHog AI
|
|
790
|
-
|
|
791
1078
|
```python
|
|
792
1079
|
from typing import Any
|
|
1080
|
+
|
|
793
1081
|
import posthog
|
|
1082
|
+
|
|
1083
|
+
|
|
794
1084
|
def scrub_pii(event: dict[str, Any]) -> dict[str, Any] | None:
|
|
795
1085
|
properties = event.get("properties", {})
|
|
1086
|
+
|
|
796
1087
|
if "email" in properties:
|
|
797
1088
|
email = properties["email"]
|
|
798
1089
|
properties["email"] = f"***@{email.split('@', 1)[1]}" if "@" in email else "***"
|
|
1090
|
+
|
|
799
1091
|
if event.get("event") == "test_event":
|
|
800
1092
|
return None
|
|
1093
|
+
|
|
801
1094
|
return event
|
|
1095
|
+
|
|
1096
|
+
|
|
802
1097
|
client = posthog.Client(
|
|
803
1098
|
"<ph_project_api_key>",
|
|
804
1099
|
before_send=scrub_pii,
|
|
@@ -811,48 +1106,50 @@ If your callback raises an exception, the SDK logs the error and continues with
|
|
|
811
1106
|
|
|
812
1107
|
You can use the Python or Node SDK to run [historical migrations](/docs/migrate.md) of data into PostHog. To do so, set the `historical_migration` option to `true` when initializing the client.
|
|
813
1108
|
|
|
814
|
-
PostHog AI
|
|
815
|
-
|
|
816
1109
|
### Python
|
|
817
1110
|
|
|
818
1111
|
```python
|
|
819
1112
|
from posthog import Posthog
|
|
820
1113
|
from datetime import datetime
|
|
1114
|
+
|
|
821
1115
|
posthog = Posthog(
|
|
822
1116
|
'<ph_project_token>',
|
|
823
1117
|
host='https://us.i.posthog.com',
|
|
824
1118
|
debug=True,
|
|
825
1119
|
historical_migration=True
|
|
826
1120
|
)
|
|
1121
|
+
|
|
827
1122
|
events = [
|
|
828
1123
|
{
|
|
829
1124
|
"event": "batched_event_name",
|
|
830
|
-
"
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
}
|
|
1125
|
+
"distinct_id": "user_id",
|
|
1126
|
+
"timestamp": datetime.fromisoformat("2024-04-02T12:00:00+00:00"),
|
|
1127
|
+
"properties": {"account_type": "pro"}
|
|
834
1128
|
},
|
|
835
1129
|
{
|
|
836
1130
|
"event": "batched_event_name",
|
|
837
|
-
"
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
}
|
|
1131
|
+
"distinct_id": "user_id",
|
|
1132
|
+
"timestamp": datetime.fromisoformat("2024-04-03T09:30:00+00:00"),
|
|
1133
|
+
"properties": {"account_type": "pro"}
|
|
841
1134
|
}
|
|
842
1135
|
]
|
|
1136
|
+
|
|
843
1137
|
for event in events:
|
|
844
1138
|
posthog.capture(
|
|
845
|
-
|
|
846
|
-
|
|
1139
|
+
event["event"],
|
|
1140
|
+
distinct_id=event["distinct_id"],
|
|
847
1141
|
properties=event["properties"],
|
|
848
|
-
timestamp=event["
|
|
1142
|
+
timestamp=event["timestamp"],
|
|
849
1143
|
)
|
|
1144
|
+
|
|
1145
|
+
posthog.shutdown()
|
|
850
1146
|
```
|
|
851
1147
|
|
|
852
1148
|
### Node.js
|
|
853
1149
|
|
|
854
1150
|
```javascript
|
|
855
1151
|
import { PostHog } from 'posthog-node'
|
|
1152
|
+
|
|
856
1153
|
const client = new PostHog(
|
|
857
1154
|
'<ph_project_token>',
|
|
858
1155
|
{
|
|
@@ -860,28 +1157,40 @@ const client = new PostHog(
|
|
|
860
1157
|
historicalMigration: true
|
|
861
1158
|
}
|
|
862
1159
|
)
|
|
1160
|
+
|
|
863
1161
|
client.debug()
|
|
1162
|
+
|
|
864
1163
|
client.capture({
|
|
865
1164
|
event: "batched_event_name",
|
|
866
1165
|
distinctId: "user_id",
|
|
867
1166
|
properties: {},
|
|
868
|
-
timestamp: "2024-04-03T12:00:00Z"
|
|
1167
|
+
timestamp: new Date("2024-04-03T12:00:00Z")
|
|
869
1168
|
})
|
|
1169
|
+
|
|
870
1170
|
client.capture({
|
|
871
1171
|
event: "batched_event_name",
|
|
872
1172
|
distinctId: "user_id",
|
|
873
1173
|
properties: {},
|
|
874
|
-
timestamp: "2024-04-03T13:00:00Z"
|
|
1174
|
+
timestamp: new Date("2024-04-03T13:00:00Z")
|
|
875
1175
|
})
|
|
1176
|
+
|
|
876
1177
|
await client.shutdown()
|
|
877
1178
|
```
|
|
878
1179
|
|
|
879
1180
|
## Serverless environments (Render/Lambda/...)
|
|
880
1181
|
|
|
881
|
-
|
|
1182
|
+
### Synchronous `Posthog`
|
|
1183
|
+
|
|
1184
|
+
By default, the synchronous `Posthog` client buffers events before sending them to the capture endpoint. This can lead to lost events if the platform terminates the Python process before the buffer is fully flushed. To avoid this, you can either:
|
|
1185
|
+
|
|
1186
|
+
- Call `posthog.shutdown()` before the process ends. This blocking call attempts to deliver queued events and cleans up the client.
|
|
1187
|
+
- Enable `sync_mode` when initializing the client so each `posthog.capture()` call attempts delivery before it returns.
|
|
1188
|
+
|
|
1189
|
+
If you use [distributed tracing](#distributed-tracing), `sync_mode` doesn't apply to spans. Call `posthog.flush()` before the handler returns – see [Shutdown and short-lived processes](#shutdown-and-short-lived-processes).
|
|
1190
|
+
|
|
1191
|
+
### Asyncio `AsyncPosthog`
|
|
882
1192
|
|
|
883
|
-
|
|
884
|
-
- Enable the `sync_mode` option when initializing the client, so that all calls to `posthog.capture()` become synchronous.
|
|
1193
|
+
Keep one `AsyncPosthog` client for the lifetime of your application. Use buffered `capture()` by default, or `await capture_immediate()` when one invocation must wait for an event's delivery attempt. Call `await posthog.shutdown()` once during application cleanup. Don't shut down the client after each request.
|
|
885
1194
|
|
|
886
1195
|
## Django
|
|
887
1196
|
|