diff --git a/.sampo/changesets/capture-v1-major.md b/.sampo/changesets/capture-v1-major.md index 9271aee97..b34e2a802 100644 --- a/.sampo/changesets/capture-v1-major.md +++ b/.sampo/changesets/capture-v1-major.md @@ -2,4 +2,4 @@ pypi/posthog: major --- -Capture v1 is the only capture path. Events and AI events send to the capture v1 endpoints, and the legacy v0 capture path is removed. See the migration guide for breaking changes. +Capture v1 is the only capture path. Events and AI events send to the capture v1 endpoints, and the legacy v0 capture path is removed. See the [migration guide](https://github.com/PostHog/posthog-python/blob/main/docs/migration-7.x-to-8.0.md) for breaking changes. diff --git a/.sampo/changesets/openfeature-provider-posthog-8.md b/.sampo/changesets/openfeature-provider-posthog-8.md new file mode 100644 index 000000000..02e3f2e00 --- /dev/null +++ b/.sampo/changesets/openfeature-provider-posthog-8.md @@ -0,0 +1,5 @@ +--- +pypi/openfeature-provider-posthog: minor +--- + +Supports posthog 8.x as well as 7.x. diff --git a/AGENTS.md b/AGENTS.md index 4d67e9efc..0e40e181c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -23,7 +23,7 @@ Follow [Public API changes](./CONTRIBUTING.md#public-api-changes). As an agent, Before changing capture configuration, serialization, routing, or retries, read the relevant implementation and tests. -Capture v1 is the only capture protocol (`capture` posts to `/i/v1/analytics/events`, `capture_ai` to `/i/v1/ai/events`); strictly typed v1 options and `$set`/`$set_once` relocation; compression (gzip, zlib-wrapped deflate, optional zstd, default none), set per lane by `capture_compression` and `capture_ai_compression`; partial-only per-event retries with stable identity; accumulated drop reporting even on 2xx; terminal v1 `429`; `Retry-After` as a minimum bounded by the shared 30s ceiling; and inline blocking retries with `sync_mode=True`. +Capture v1 is the only capture protocol (`capture` posts to `/i/v1/analytics/events`, `capture_ai` to `/i/v1/ai/events`); per-event `options` sent as given, layered over `super_options` and context options, with legacy `$` option properties hoisted once after `before_send`; `$set`/`$set_once` relocation; compression (gzip, zlib-wrapped deflate, optional zstd, default none), set per lane by `capture_compression` and `capture_ai_compression`; partial-only per-event retries with stable identity; accumulated drop reporting even on 2xx; terminal v1 `429`; `Retry-After` as a minimum bounded by the shared 30s ceiling; and inline blocking retries with `sync_mode=True`. ## Mirror and build safety diff --git a/README.md b/README.md index 364101f01..f14f16be6 100644 --- a/README.md +++ b/README.md @@ -18,13 +18,15 @@ SDK usage examples and code snippets live in the official documentation so they | SDK Version | Python Versions Supported | Notes | | ------------- | ---------------------------- | -------------------------- | -| 7.3.1+ | 3.10, 3.11, 3.12, 3.13, 3.14 | Added Python 3.14 support | +| 8.0.0+ | 3.10, 3.11, 3.12, 3.13, 3.14 | Capture v1 only, see the [migration guide](docs/migration-7.x-to-8.0.md) | +| 7.3.1 - 7.x | 3.10, 3.11, 3.12, 3.13, 3.14 | Added Python 3.14 support | | 7.0.0 - 7.0.1 | 3.10, 3.11, 3.12, 3.13 | Dropped Python 3.9 support | | 4.0.1 - 6.x | 3.9, 3.10, 3.11, 3.12, 3.13 | Python 3.9+ required | ## Documentation - [Python library docs](https://posthog.com/docs/libraries/python) +- [Migrating from 7.x to 8.0](docs/migration-7.x-to-8.0.md) - [Django framework docs](https://posthog.com/docs/libraries/django) - [Flask framework docs](https://posthog.com/docs/libraries/flask) - [OpenFeature provider docs](https://posthog.com/docs/feature-flags/installation/openfeature) — use PostHog flags through the [OpenFeature](https://openfeature.dev) Python SDK diff --git a/docs/migration-7.x-to-8.0.md b/docs/migration-7.x-to-8.0.md new file mode 100644 index 000000000..a141465fc --- /dev/null +++ b/docs/migration-7.x-to-8.0.md @@ -0,0 +1,177 @@ +# Migrating from posthog 7.x to 8.0 + +posthog 8.0 sends every event through capture v1 and removes the legacy capture path. +Most apps upgrade without code changes. +Read the checklist first, then the sections that apply to you. + +## Checklist + +You need to change code if your app does any of these: + +- passes `Client` or `AsyncPosthog` constructor arguments by position after `host` +- sets `capture_mode`, `POSTHOG_CAPTURE_MODE` or `gzip` +- imports `CaptureV1Error`, `posthog.capture_v1`, `request.batch_post`, `EVENTS_ENDPOINT` or `AI_EVENTS_ENDPOINT` +- sends events to a self-hosted PostHog that does not serve the capture v1 endpoints +- reuses one event `uuid` for more than one event +- sets `$process_person_profile` to turn person processing on for events without a distinct ID +- passes strings such as `"true"` for `$cookieless_mode`, `$ignore_sent_at` or `$process_person_profile` +- expects `super_properties` to override properties passed to a single call, including `$set` and `$groups` +- sets `$is_server`, `$geoip_disable` or a system property such as `$os` and expects the SDK's value to win +- tests the AI integrations with a mock client and asserts on `capture` +- passes its own client object to the AI integrations + +## Endpoints + +| Method | Endpoint | +| --- | --- | +| `capture`, `set`, `set_once`, `alias`, `group_identify`, `capture_exception` | `/i/v1/analytics/events` | +| `capture_ai`, `capture_ai_immediate`, and every built-in AI integration | `/i/v1/ai/events` | + +If you send events to a self-hosted PostHog, check that it serves both endpoints before you upgrade. +An endpoint that is not served drops every event sent to it. + +## Removed options and APIs + +| Removed | Use instead | +| --- | --- | +| `capture_mode`, `CaptureMode`, `POSTHOG_CAPTURE_MODE` | Nothing. Capture v1 is the only path. Setting `posthog.capture_mode` on the module has no effect. | +| `gzip=True` | `capture_compression=CaptureCompression.GZIP`. The default is no compression. `POSTHOG_CAPTURE_COMPRESSION` still works. | +| `CaptureV1Error` (`from posthog.capture_v1 import CaptureV1Error`) | `CaptureError` (`from posthog import CaptureError`). There is no alias. | +| `posthog.capture_v1` | `posthog.capture_event` and `posthog.capture_send` | +| `request.batch_post`, `async_batch_post`, `EVENTS_ENDPOINT`, `AI_EVENTS_ENDPOINT` | Nothing. Send events through a client. | +| The `gzip` parameter of `request.post` and `request.flags` | Nothing. | +| The `backoff` dependency | Add it to your own requirements if your code imports it. | + +The `posthoganalytics` package has the same changes. For example, import `CaptureError` from `posthoganalytics`. + +Constructor arguments after `host` are keyword-only: + +```python +# 7.x +Posthog("", "https://us.i.posthog.com", True) + +# 8.0 +Posthog("", host="https://us.i.posthog.com", debug=True) +``` + +## Errors and `on_error` + +Capture v1 returns a result for every event, even when the request succeeds. +An event can be dropped inside a 2xx response, for example when it is over a product quota. +8.0 reports those events as failures. +When the whole request is over the billing limit, capture returns a `402` with no per-event results instead. + +- Every capture failure is a `CaptureError`, a subclass of `APIError`. It has these fields: + - `status`: the HTTP status, or `0` when the request never got a response. With `0`, `__cause__` holds the network error. In 7.x, `on_error` received the raw `requests` exception. + - `endpoint`, `request_id` and `attempts` + - `drops`: `(uuid, details)` pairs for the events capture dropped in a 2xx response + - `retry_exhausted`: the uuids still waiting for a retry after the last attempt + - `event_results`: a `CaptureEventResult(result, details)` for each uuid that got a result in a 2xx response, from `posthog.capture_send`. An event missing from it never got a result. + - `verdict_summary()`: counts such as `drop/llm_events_over_quota=1, retry/not_persisted=1` +- `on_error(error, batch)` receives every failure, including in `sync_mode` and from `AsyncPosthog.capture_immediate`. +- With `on_error` set, the SDK logs nothing for a failed batch. +- Without `on_error`, the SDK logs one line per failed batch, for example `2 event(s) not persisted by /i/v1/analytics/events: ...`. The line never contains event content or the server's response text. +- The 7.x lines `error uploading: ...`, `async capture upload failed (...)` and `Immediate async capture failed (...)` are gone. Update any log-based alerts that match them. +- In `sync_mode`, a failed `capture()` calls `on_error` and returns `None`. With `debug=True` it calls `on_error` and then re-raises the error. +- If a capture inside `on_error` fails, the SDK logs the failure and does not call `on_error` again. +- The SDK retries `408`, `500`, `502`, `503` and `504` responses and network errors. Other error statuses fail the batch at once, including `429` and the other 5xx codes. 7.x also retried `429` and every 5xx. Capture itself does not return `429`; a proxy in front of it can. +- Retries start at 100 ms and double each attempt, up to 30 seconds. A `Retry-After` header sets a minimum wait, up to 30 seconds. The `Consumer` default is 3 retries, down from 10. + +## Event uuids + +- Each event needs its own uuid. Capture rejects a whole batch that contains the same uuid twice, so the other events in that batch are lost too. +- The SDK accepts a uuid with hyphens, 32 hex digits, `{...}` braces or a `urn:uuid:` prefix, in any case. It sends the lowercase hyphenated form and returns that form from `capture`. +- Any other value is replaced with a generated uuid, and the SDK logs an error. This applies to `AsyncPosthog` too. +- Generated uuids are UUIDv7. + +## Session and window IDs + +Capture v1 sends `$session_id` and `$window_id` as top-level event fields, not as properties. +The SDK moves them out of `properties` for you. + +- A string is sent as given, including `""`. +- `None` counts as unset and is removed. +- Any other value is removed and not sent, because capture would reject the whole batch. 7.x sent it as a property. + +## Event options + +Capture v1 sends processing options in an `options` object, next to `properties`. + +- `capture`, `capture_ai`, `set`, `set_once`, `alias`, `group_identify` and `capture_exception` take an `options` argument: + + ```python + posthog.capture("signed_up", distinct_id="user-1", options={"cookieless_mode": True}) + ``` + +- `super_options`, a new `Client`, `AsyncPosthog` and module setting, sets options on every event, like `super_properties`. +- `set_context_option(key, value)` sets an option for the current context, like `tag()`. `get_context_options()` returns the options of the current context. Both exist on `Client` and as module functions. `AsyncPosthog` has no context methods, like it has no `tag()`, so call the module functions. They apply to every client in the context. +- Context tags and options reach `capture`, `capture_ai` and `capture_exception`. Context options also reach `set` and `set_once`. Neither reaches `alias` or `group_identify`. `super_properties` and `super_options` reach every method. +- Options are sent as given. PostHog validates them. +- An `options` value that is not a dict is logged as an error and ignored. The event is still sent. +- The legacy properties still work. They fill the matching option only when it is unset, and the SDK always removes them from `properties`. Their values are no longer converted, so pass `True` or `False`, not `"true"`. + + | Legacy property | Option | + | --- | --- | + | `$cookieless_mode` | `cookieless_mode` | + | `$ignore_sent_at` | `disable_skew_correction` | + | `$product_tour_id` | `product_tour_id` | + | `$process_person_profile` | `process_person_profile` | + +- An option set at any layer wins over its legacy property set at any layer. For example, `super_options={"cookieless_mode": True}` wins over an event's `$cookieless_mode: False`. When you move a default to options, move the per-event overrides of that key to options too. +- A `None` option counts as unset, so a later step can fill it. + +Values apply in this order. Steps 2 to 4 fill only the options and properties that the steps before them left unset: + +1. the `options` and `properties` of the call +2. context options and tags +3. `super_options` and `super_properties` +4. values the SDK sets: `$is_server` from `is_server`, `$geoip_disable` from `disable_geoip`, system properties such as `$os` and `$python_version`, `options.process_person_profile = false` for events without a distinct ID, and `$release_id` from `POSTHOG_RELEASE_ID` +5. `before_send`, which sees the result of steps 1 to 4 and can change or remove any of it +6. legacy properties, which fill unset options and are then removed + +A property set to `None` counts as set, so no later step fills it. +`$set`, `$set_once`, `$groups` and `$group_set` fill one level deep. +When the call and a later step both set one of them to a dict, the later step adds only the keys the call left out. +For example, `super_properties={"$set": {"plan": "free", "source": "web"}}` and a call with `properties={"$set": {"plan": "pro"}}` send `{"plan": "pro", "source": "web"}`. + +These changes follow from this order: + +- Properties passed to a call now override `super_properties`. `super_properties` can no longer change `$lib` or `$lib_version`. +- A `$is_server`, `$geoip_disable` or system property such as `$os` that you set in a call or a context tag now wins over the SDK's value. In 7.x the SDK overwrote it. `super_properties` win over the SDK's value too, so `super_properties={"$geoip_disable": False}` turns GeoIP lookup on for events, even with `disable_geoip=True`. +- The `groups` argument merges into a `$groups` property of the call, and wins key by key. In 7.x it replaced the property. MCP events merge their identity's groups into a custom `$groups` property the same way. +- A `$set`, `$set_once`, `$groups` or `$group_set` in `super_properties` no longer replaces the whole value of the call. The two merge, and the call wins key by key. +- An event without a distinct ID gets `options.process_person_profile = false`. A `$process_person_profile: true` property no longer turns person processing back on. Set the option instead. +- To change an option in `before_send`, edit `options`. A legacy property that `before_send` adds does not replace an option that is already set. +- When the server enables minimal `$feature_flag_called` events, those events now keep `$window_id`, `$cookieless_mode`, `$ignore_sent_at` and `$product_tour_id`, so their options still apply. + +## AI capture + +- `capture_ai` and every built-in AI integration send to `/i/v1/ai/events`. The AI endpoint accepts events up to 8 MiB. +- `enable_full_ai_capture` now controls only content: string truncation and media redaction. It no longer chooses the endpoint. `_use_ai_lane` and `_enable_multimodal_capture` still work as aliases. +- The AI integrations call your client's `capture_ai`. A client object without `capture_ai` gets `capture` calls instead. A custom client must accept the `options` keyword argument. +- Tests that pass a `Mock` client to an AI integration must assert on `mock.capture_ai`, not `mock.capture`. +- When an AI integration or MCP falls back to a trace, run or session ID, it turns person processing off with a per-event option. This is the only option the integrations pass. It wins over `super_options`, context options and a `$process_person_profile: true` property. `before_send` can still change it. +- The AI integrations have no `options` argument. To set options on their events, use `set_context_option`, `super_options`, or a legacy property such as `$cookieless_mode` in `posthog_properties`. +- MCP's `PostHogCaptureEvent` has an `options` key. +- New `Client` and `AsyncPosthog` arguments for the AI lane: + - `capture_ai_compression`: default none. `POSTHOG_CAPTURE_COMPRESSION` does not apply to it. + - `capture_ai_max_queue_size`: default 1000 + - `capture_ai_timeout`: default 30 seconds + - `capture_ai_max_event_bytes`: default 8 MiB plus 64 KiB. You can only lower it. +- The AI lane does not use `max_queue_size`, `timeout` or `capture_compression`. An invalid `capture_ai_*` value raises `ValueError` when you create the client. +- The module-level client has no AI lane settings. To change them, create a `Client`. +- `AsyncPosthog` has `capture_ai` and `capture_ai_immediate`. +- `AsyncPosthog` sends events with `requests` in a worker thread. It uses `httpx` only for feature flags and remote config. +- `AsyncPosthog` accepts `privacy_mode` and `enable_full_ai_capture`, with the same meaning as on `Client`. An AI integration given an `AsyncPosthog` client used to always truncate and redact media. + +## Batching and size limits + +- An analytics event over 900 KiB is dropped and logged. This now applies in `sync_mode` too. +- An AI event over `capture_ai_max_event_bytes` is dropped and logged. +- A batch stops before an event that would take it past 5 MiB. A larger event is sent alone. +- `flush()` and `shutdown()` drain the analytics and AI queues at the same time. + +## OpenFeature provider + +`openfeature-provider-posthog` 0.2.0 works with posthog 7.x and 8.x. +Older provider versions require posthog below 8.0, so upgrade the provider before or with posthog. diff --git a/examples/example-ai-crewai/pyproject.toml b/examples/example-ai-crewai/pyproject.toml index 0c6cb57f8..9e655dc11 100644 --- a/examples/example-ai-crewai/pyproject.toml +++ b/examples/example-ai-crewai/pyproject.toml @@ -3,7 +3,10 @@ name = "example-ai-crewai" version = "0.1.0" requires-python = ">=3.10" dependencies = [ - "posthog==7.9.12", + "posthog", "crewai>=0.80.0", "litellm>=1.88.6", ] + +[tool.uv.sources] +posthog = { path = "../.." } diff --git a/examples/example-ai-crewai/uv.lock b/examples/example-ai-crewai/uv.lock index 7485aa704..8bcec0e08 100644 --- a/examples/example-ai-crewai/uv.lock +++ b/examples/example-ai-crewai/uv.lock @@ -10,7 +10,7 @@ resolution-markers = [ ] [options] -exclude-newer = "0001-01-01T00:00:00Z" # This has no effect and is included for backwards compatibility when using relative exclude-newer values. +exclude-newer = "2026-10-03T03:16:06.117367Z" exclude-newer-span = "P7D" [[package]] @@ -260,15 +260,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/3e/83/65ecd7449fbdc5b91de15e6a9fcb4766076da8c4b117d3eb1194582e4de4/auth0_python-5.3.0-py3-none-any.whl", hash = "sha256:251fb2f524f7bc4f88b69e48b0444d05f71769aef4f61076f2ba3573ca69dae6", size = 2038474, upload-time = "2026-04-09T13:27:03.808Z" }, ] -[[package]] -name = "backoff" -version = "2.2.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/47/d7/5bbeb12c44d7c4f2fb5b56abce497eb5ed9f34d85701de869acedd602619/backoff-2.2.1.tar.gz", hash = "sha256:03f829f5bb1923180821643f8753b0502c3b682293992485b0eef2807afa5cba", size = 17001, upload-time = "2022-10-05T19:19:32.061Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/df/73/b6e24bd22e6720ca8ee9a85a0c4a2971af8497d8f3193fa05390cbd46e09/backoff-2.2.1-py3-none-any.whl", hash = "sha256:63579f9a0628e06278f7e47b7d7d5b6ce20dc65c5e96a6f3ca99a6adca0396e8", size = 15148, upload-time = "2022-10-05T19:19:30.546Z" }, -] - [[package]] name = "bcrypt" version = "5.0.0" @@ -794,7 +785,7 @@ dependencies = [ requires-dist = [ { name = "crewai", specifier = ">=0.80.0" }, { name = "litellm", specifier = ">=1.88.6" }, - { name = "posthog", specifier = "==7.9.12" }, + { name = "posthog", directory = "../../" }, ] [[package]] @@ -802,7 +793,7 @@ name = "exceptiongroup" version = "1.3.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "typing-extensions" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" } wheels = [ @@ -1306,17 +1297,17 @@ resolution-markers = [ "python_full_version < '3.11'", ] dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, - { name = "decorator" }, - { name = "exceptiongroup" }, - { name = "jedi" }, - { name = "matplotlib-inline" }, - { name = "pexpect", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" }, - { name = "prompt-toolkit" }, - { name = "pygments" }, - { name = "stack-data" }, - { name = "traitlets" }, - { name = "typing-extensions" }, + { name = "colorama", marker = "python_full_version < '3.11' and sys_platform == 'win32'" }, + { name = "decorator", marker = "python_full_version < '3.11'" }, + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "jedi", marker = "python_full_version < '3.11'" }, + { name = "matplotlib-inline", marker = "python_full_version < '3.11'" }, + { name = "pexpect", marker = "python_full_version < '3.11' and sys_platform != 'emscripten' and sys_platform != 'win32'" }, + { name = "prompt-toolkit", marker = "python_full_version < '3.11'" }, + { name = "pygments", marker = "python_full_version < '3.11'" }, + { name = "stack-data", marker = "python_full_version < '3.11'" }, + { name = "traitlets", marker = "python_full_version < '3.11'" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/40/18/f8598d287006885e7136451fdea0755af4ebcbfe342836f24deefaed1164/ipython-8.39.0.tar.gz", hash = "sha256:4110ae96012c379b8b6db898a07e186c40a2a1ef5d57a7fa83166047d9da7624", size = 5513971, upload-time = "2026-03-27T10:02:13.94Z" } wheels = [ @@ -1331,17 +1322,17 @@ resolution-markers = [ "python_full_version == '3.11.*'", ] dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, - { name = "decorator" }, - { name = "ipython-pygments-lexers" }, - { name = "jedi" }, - { name = "matplotlib-inline" }, - { name = "pexpect", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" }, - { name = "prompt-toolkit" }, - { name = "pygments" }, - { name = "stack-data" }, - { name = "traitlets" }, - { name = "typing-extensions" }, + { name = "colorama", marker = "python_full_version == '3.11.*' and sys_platform == 'win32'" }, + { name = "decorator", marker = "python_full_version == '3.11.*'" }, + { name = "ipython-pygments-lexers", marker = "python_full_version == '3.11.*'" }, + { name = "jedi", marker = "python_full_version == '3.11.*'" }, + { name = "matplotlib-inline", marker = "python_full_version == '3.11.*'" }, + { name = "pexpect", marker = "python_full_version == '3.11.*' and sys_platform != 'emscripten' and sys_platform != 'win32'" }, + { name = "prompt-toolkit", marker = "python_full_version == '3.11.*'" }, + { name = "pygments", marker = "python_full_version == '3.11.*'" }, + { name = "stack-data", marker = "python_full_version == '3.11.*'" }, + { name = "traitlets", marker = "python_full_version == '3.11.*'" }, + { name = "typing-extensions", marker = "python_full_version == '3.11.*'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/c5/25/daae0e764047b0a2480c7bbb25d48f4f509b5818636562eeac145d06dfee/ipython-9.10.1.tar.gz", hash = "sha256:e170e9b2a44312484415bdb750492699bf329233b03f2557a9692cce6466ada4", size = 4426663, upload-time = "2026-03-27T09:53:26.244Z" } wheels = [ @@ -1358,16 +1349,16 @@ resolution-markers = [ "python_full_version == '3.12.*'", ] dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, - { name = "decorator" }, - { name = "ipython-pygments-lexers" }, - { name = "jedi" }, - { name = "matplotlib-inline" }, - { name = "pexpect", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" }, - { name = "prompt-toolkit" }, - { name = "pygments" }, - { name = "stack-data" }, - { name = "traitlets" }, + { name = "colorama", marker = "python_full_version >= '3.12' and sys_platform == 'win32'" }, + { name = "decorator", marker = "python_full_version >= '3.12'" }, + { name = "ipython-pygments-lexers", marker = "python_full_version >= '3.12'" }, + { name = "jedi", marker = "python_full_version >= '3.12'" }, + { name = "matplotlib-inline", marker = "python_full_version >= '3.12'" }, + { name = "pexpect", marker = "python_full_version >= '3.12' and sys_platform != 'emscripten' and sys_platform != 'win32'" }, + { name = "prompt-toolkit", marker = "python_full_version >= '3.12'" }, + { name = "pygments", marker = "python_full_version >= '3.12'" }, + { name = "stack-data", marker = "python_full_version >= '3.12'" }, + { name = "traitlets", marker = "python_full_version >= '3.12'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/3a/73/7114f80a8f9cabdb13c27732dce24af945b2923dcab80723602f7c8bc2d8/ipython-9.12.0.tar.gz", hash = "sha256:01daa83f504b693ba523b5a407246cabde4eb4513285a3c6acaff11a66735ee4", size = 4428879, upload-time = "2026-03-27T09:42:45.312Z" } wheels = [ @@ -1379,7 +1370,7 @@ name = "ipython-pygments-lexers" version = "1.1.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pygments" }, + { name = "pygments", marker = "python_full_version >= '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ef/4c/5dd1d8af08107f88c7f741ead7a40854b8ac24ddf9ae850afbcf698aa552/ipython_pygments_lexers-1.1.1.tar.gz", hash = "sha256:09c0138009e56b6854f9535736f4171d855c8c08a563a0dcd8022f78355c7e81", size = 8393, upload-time = "2025-01-17T11:24:34.505Z" } wheels = [ @@ -2620,20 +2611,75 @@ wheels = [ [[package]] name = "posthog" -version = "7.9.12" -source = { registry = "https://pypi.org/simple" } +version = "7.64.1" +source = { directory = "../../" } dependencies = [ - { name = "backoff" }, { name = "distro" }, - { name = "python-dateutil" }, { name = "requests" }, - { name = "six" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/1c/a7/2865487853061fbd62383492237b546d2d8f7c1846272350d2b9e14138cd/posthog-7.9.12.tar.gz", hash = "sha256:ebabf2eb2e1c1fbf22b0759df4644623fa43cc6c9dcbe9fd429b7937d14251ec", size = 176828, upload-time = "2026-03-12T09:01:15.184Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/65/a9/7a803aed5a5649cf78ea7b31e90d0080181ba21f739243e1741a1e607f1f/posthog-7.9.12-py3-none-any.whl", hash = "sha256:7175bd1698a566bfea98a016c64e3456399f8046aeeca8f1d04ae5bf6c5a38d0", size = 202469, upload-time = "2026-03-12T09:01:13.38Z" }, -] + +[package.metadata] +requires-dist = [ + { name = "anthropic", marker = "extra == 'test'", specifier = ">=0.72" }, + { name = "claude-agent-sdk", marker = "extra == 'test'" }, + { name = "coverage", marker = "extra == 'test'" }, + { name = "distro", specifier = ">=1.5.0" }, + { name = "django", marker = "extra == 'test'", specifier = ">=5.2.15,<6.0" }, + { name = "django-stubs", marker = "extra == 'dev'" }, + { name = "fastmcp", marker = "extra == 'test'", specifier = ">=2.0" }, + { name = "freezegun", marker = "extra == 'test'", specifier = "==1.5.1" }, + { name = "gevent", marker = "implementation_name == 'cpython' and extra == 'test'", specifier = ">=25.4.1" }, + { name = "google-genai", marker = "extra == 'test'" }, + { name = "griffe", marker = "extra == 'dev'" }, + { name = "httpx", marker = "extra == 'async'", specifier = ">=0.27.0,<1.0" }, + { name = "jsonschema", marker = "extra == 'test'", specifier = ">=4.0" }, + { name = "langchain", marker = "extra == 'langchain'", specifier = ">=1.3.9" }, + { name = "langchain", marker = "extra == 'test'", specifier = ">=1.3.9" }, + { name = "langchain-anthropic", marker = "extra == 'test'", specifier = ">=1.0" }, + { name = "langchain-community", marker = "extra == 'test'", specifier = ">=0.4" }, + { name = "langchain-core", marker = "extra == 'test'", specifier = ">=1.0" }, + { name = "langchain-openai", marker = "extra == 'test'", specifier = ">=1.1.14" }, + { name = "langgraph", marker = "extra == 'test'", specifier = ">=1.0" }, + { name = "langgraph-checkpoint", marker = "extra == 'test'", specifier = ">=4.1.1" }, + { name = "lxml", marker = "extra == 'dev'" }, + { name = "mcp", marker = "extra == 'test'", specifier = ">=1.28.1,<2" }, + { 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 == 'otel'", specifier = ">=1.20.0" }, + { name = "opentelemetry-exporter-otlp-proto-http", marker = "extra == 'test'", specifier = ">=1.20.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'" }, + { name = "parameterized", marker = "extra == 'test'", specifier = ">=0.8.1" }, + { name = "pre-commit", marker = "extra == 'dev'" }, + { name = "pydantic", marker = "extra == 'dev'", specifier = ">=2.12.0" }, + { name = "pydantic", marker = "extra == 'test'", specifier = ">=2.12.0" }, + { name = "pytest", marker = "extra == 'test'" }, + { name = "pytest-asyncio", marker = "extra == 'test'" }, + { name = "pytest-bdd", marker = "extra == 'test'", specifier = ">=8.1.0" }, + { name = "pytest-timeout", marker = "extra == 'test'" }, + { name = "python-dateutil", marker = "extra == 'test'", specifier = ">=2.9.0.post0" }, + { name = "requests", specifier = ">=2.7,<3.0" }, + { name = "ruff", marker = "extra == 'dev'" }, + { name = "setuptools", marker = "extra == 'dev'" }, + { name = "tiktoken", marker = "extra == 'test'", specifier = ">=0.12.0" }, + { name = "tomli", marker = "extra == 'dev'" }, + { name = "tomli-w", marker = "extra == 'dev'" }, + { name = "twine", marker = "extra == 'dev'" }, + { name = "types-requests", marker = "extra == 'dev'" }, + { name = "types-setuptools", marker = "extra == 'dev'" }, + { name = "typing-extensions", specifier = ">=4.2.0" }, + { name = "tzdata", marker = "extra == 'test'" }, + { name = "wheel", marker = "extra == 'dev'" }, + { 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"] + +[package.metadata.requires-dev] +dev = [{ name = "claude-agent-sdk", specifier = ">=0.1.50" }] [[package]] name = "prompt-toolkit" diff --git a/examples/example-ai-dspy/pyproject.toml b/examples/example-ai-dspy/pyproject.toml index a78ea7b6d..48e96ffad 100644 --- a/examples/example-ai-dspy/pyproject.toml +++ b/examples/example-ai-dspy/pyproject.toml @@ -3,7 +3,10 @@ name = "example-ai-dspy" version = "0.1.0" requires-python = ">=3.10" dependencies = [ - "posthog==7.9.12", + "posthog", "dspy>=2.5.0", "litellm==1.88.6", ] + +[tool.uv.sources] +posthog = { path = "../.." } diff --git a/examples/example-ai-dspy/uv.lock b/examples/example-ai-dspy/uv.lock index 8b14811a9..a4228f087 100644 --- a/examples/example-ai-dspy/uv.lock +++ b/examples/example-ai-dspy/uv.lock @@ -7,7 +7,7 @@ resolution-markers = [ ] [options] -exclude-newer = "0001-01-01T00:00:00Z" # This has no effect and is included for backwards compatibility when using relative exclude-newer values. +exclude-newer = "2026-10-03T03:16:08.499203Z" exclude-newer-span = "P7D" [[package]] @@ -246,15 +246,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309", size = 67548, upload-time = "2026-03-19T14:22:23.645Z" }, ] -[[package]] -name = "backoff" -version = "2.2.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/47/d7/5bbeb12c44d7c4f2fb5b56abce497eb5ed9f34d85701de869acedd602619/backoff-2.2.1.tar.gz", hash = "sha256:03f829f5bb1923180821643f8753b0502c3b682293992485b0eef2807afa5cba", size = 17001, upload-time = "2022-10-05T19:19:32.061Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/df/73/b6e24bd22e6720ca8ee9a85a0c4a2971af8497d8f3193fa05390cbd46e09/backoff-2.2.1-py3-none-any.whl", hash = "sha256:63579f9a0628e06278f7e47b7d7d5b6ce20dc65c5e96a6f3ca99a6adca0396e8", size = 15148, upload-time = "2022-10-05T19:19:30.546Z" }, -] - [[package]] name = "cachetools" version = "7.0.5" @@ -482,7 +473,7 @@ dependencies = [ requires-dist = [ { name = "dspy", specifier = ">=2.5.0" }, { name = "litellm", specifier = "==1.88.6" }, - { name = "posthog", specifier = "==7.9.12" }, + { name = "posthog", directory = "../../" }, ] [[package]] @@ -490,7 +481,7 @@ name = "exceptiongroup" version = "1.3.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "typing-extensions" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" } wheels = [ @@ -1616,20 +1607,75 @@ wheels = [ [[package]] name = "posthog" -version = "7.9.12" -source = { registry = "https://pypi.org/simple" } +version = "7.64.1" +source = { directory = "../../" } dependencies = [ - { name = "backoff" }, { name = "distro" }, - { name = "python-dateutil" }, { name = "requests" }, - { name = "six" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/1c/a7/2865487853061fbd62383492237b546d2d8f7c1846272350d2b9e14138cd/posthog-7.9.12.tar.gz", hash = "sha256:ebabf2eb2e1c1fbf22b0759df4644623fa43cc6c9dcbe9fd429b7937d14251ec", size = 176828, upload-time = "2026-03-12T09:01:15.184Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/65/a9/7a803aed5a5649cf78ea7b31e90d0080181ba21f739243e1741a1e607f1f/posthog-7.9.12-py3-none-any.whl", hash = "sha256:7175bd1698a566bfea98a016c64e3456399f8046aeeca8f1d04ae5bf6c5a38d0", size = 202469, upload-time = "2026-03-12T09:01:13.38Z" }, -] + +[package.metadata] +requires-dist = [ + { name = "anthropic", marker = "extra == 'test'", specifier = ">=0.72" }, + { name = "claude-agent-sdk", marker = "extra == 'test'" }, + { name = "coverage", marker = "extra == 'test'" }, + { name = "distro", specifier = ">=1.5.0" }, + { name = "django", marker = "extra == 'test'", specifier = ">=5.2.15,<6.0" }, + { name = "django-stubs", marker = "extra == 'dev'" }, + { name = "fastmcp", marker = "extra == 'test'", specifier = ">=2.0" }, + { name = "freezegun", marker = "extra == 'test'", specifier = "==1.5.1" }, + { name = "gevent", marker = "implementation_name == 'cpython' and extra == 'test'", specifier = ">=25.4.1" }, + { name = "google-genai", marker = "extra == 'test'" }, + { name = "griffe", marker = "extra == 'dev'" }, + { name = "httpx", marker = "extra == 'async'", specifier = ">=0.27.0,<1.0" }, + { name = "jsonschema", marker = "extra == 'test'", specifier = ">=4.0" }, + { name = "langchain", marker = "extra == 'langchain'", specifier = ">=1.3.9" }, + { name = "langchain", marker = "extra == 'test'", specifier = ">=1.3.9" }, + { name = "langchain-anthropic", marker = "extra == 'test'", specifier = ">=1.0" }, + { name = "langchain-community", marker = "extra == 'test'", specifier = ">=0.4" }, + { name = "langchain-core", marker = "extra == 'test'", specifier = ">=1.0" }, + { name = "langchain-openai", marker = "extra == 'test'", specifier = ">=1.1.14" }, + { name = "langgraph", marker = "extra == 'test'", specifier = ">=1.0" }, + { name = "langgraph-checkpoint", marker = "extra == 'test'", specifier = ">=4.1.1" }, + { name = "lxml", marker = "extra == 'dev'" }, + { name = "mcp", marker = "extra == 'test'", specifier = ">=1.28.1,<2" }, + { 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 == 'otel'", specifier = ">=1.20.0" }, + { name = "opentelemetry-exporter-otlp-proto-http", marker = "extra == 'test'", specifier = ">=1.20.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'" }, + { name = "parameterized", marker = "extra == 'test'", specifier = ">=0.8.1" }, + { name = "pre-commit", marker = "extra == 'dev'" }, + { name = "pydantic", marker = "extra == 'dev'", specifier = ">=2.12.0" }, + { name = "pydantic", marker = "extra == 'test'", specifier = ">=2.12.0" }, + { name = "pytest", marker = "extra == 'test'" }, + { name = "pytest-asyncio", marker = "extra == 'test'" }, + { name = "pytest-bdd", marker = "extra == 'test'", specifier = ">=8.1.0" }, + { name = "pytest-timeout", marker = "extra == 'test'" }, + { name = "python-dateutil", marker = "extra == 'test'", specifier = ">=2.9.0.post0" }, + { name = "requests", specifier = ">=2.7,<3.0" }, + { name = "ruff", marker = "extra == 'dev'" }, + { name = "setuptools", marker = "extra == 'dev'" }, + { name = "tiktoken", marker = "extra == 'test'", specifier = ">=0.12.0" }, + { name = "tomli", marker = "extra == 'dev'" }, + { name = "tomli-w", marker = "extra == 'dev'" }, + { name = "twine", marker = "extra == 'dev'" }, + { name = "types-requests", marker = "extra == 'dev'" }, + { name = "types-setuptools", marker = "extra == 'dev'" }, + { name = "typing-extensions", specifier = ">=4.2.0" }, + { name = "tzdata", marker = "extra == 'test'" }, + { name = "wheel", marker = "extra == 'dev'" }, + { 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"] + +[package.metadata.requires-dev] +dev = [{ name = "claude-agent-sdk", specifier = ">=0.1.50" }] [[package]] name = "propcache" @@ -1887,18 +1933,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" }, ] -[[package]] -name = "python-dateutil" -version = "2.9.0.post0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "six" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, -] - [[package]] name = "python-dotenv" version = "1.2.3" @@ -2266,15 +2300,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" }, ] -[[package]] -name = "six" -version = "1.17.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, -] - [[package]] name = "sniffio" version = "1.3.1" diff --git a/examples/example-ai-litellm/pyproject.toml b/examples/example-ai-litellm/pyproject.toml index 0691dbfb5..d3e0b35da 100644 --- a/examples/example-ai-litellm/pyproject.toml +++ b/examples/example-ai-litellm/pyproject.toml @@ -3,6 +3,9 @@ name = "example-ai-litellm" version = "0.1.0" requires-python = ">=3.10" dependencies = [ - "posthog==7.9.12", + "posthog", "litellm==1.88.6", ] + +[tool.uv.sources] +posthog = { path = "../.." } diff --git a/examples/example-ai-litellm/uv.lock b/examples/example-ai-litellm/uv.lock index a17bcd98f..5c9ae49f0 100644 --- a/examples/example-ai-litellm/uv.lock +++ b/examples/example-ai-litellm/uv.lock @@ -3,7 +3,7 @@ revision = 3 requires-python = ">=3.10" [options] -exclude-newer = "0001-01-01T00:00:00Z" # This has no effect and is included for backwards compatibility when using relative exclude-newer values. +exclude-newer = "2026-10-03T03:16:09.116916Z" exclude-newer-span = "P7D" [[package]] @@ -215,15 +215,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309", size = 67548, upload-time = "2026-03-19T14:22:23.645Z" }, ] -[[package]] -name = "backoff" -version = "2.2.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/47/d7/5bbeb12c44d7c4f2fb5b56abce497eb5ed9f34d85701de869acedd602619/backoff-2.2.1.tar.gz", hash = "sha256:03f829f5bb1923180821643f8753b0502c3b682293992485b0eef2807afa5cba", size = 17001, upload-time = "2022-10-05T19:19:32.061Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/df/73/b6e24bd22e6720ca8ee9a85a0c4a2971af8497d8f3193fa05390cbd46e09/backoff-2.2.1-py3-none-any.whl", hash = "sha256:63579f9a0628e06278f7e47b7d7d5b6ce20dc65c5e96a6f3ca99a6adca0396e8", size = 15148, upload-time = "2022-10-05T19:19:30.546Z" }, -] - [[package]] name = "certifi" version = "2026.2.25" @@ -380,7 +371,7 @@ dependencies = [ [package.metadata] requires-dist = [ { name = "litellm", specifier = "==1.88.6" }, - { name = "posthog", specifier = "==7.9.12" }, + { name = "posthog", directory = "../../" }, ] [[package]] @@ -388,7 +379,7 @@ name = "exceptiongroup" version = "1.3.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "typing-extensions" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" } wheels = [ @@ -1183,20 +1174,75 @@ wheels = [ [[package]] name = "posthog" -version = "7.9.12" -source = { registry = "https://pypi.org/simple" } +version = "7.64.1" +source = { directory = "../../" } dependencies = [ - { name = "backoff" }, { name = "distro" }, - { name = "python-dateutil" }, { name = "requests" }, - { name = "six" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/1c/a7/2865487853061fbd62383492237b546d2d8f7c1846272350d2b9e14138cd/posthog-7.9.12.tar.gz", hash = "sha256:ebabf2eb2e1c1fbf22b0759df4644623fa43cc6c9dcbe9fd429b7937d14251ec", size = 176828, upload-time = "2026-03-12T09:01:15.184Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/65/a9/7a803aed5a5649cf78ea7b31e90d0080181ba21f739243e1741a1e607f1f/posthog-7.9.12-py3-none-any.whl", hash = "sha256:7175bd1698a566bfea98a016c64e3456399f8046aeeca8f1d04ae5bf6c5a38d0", size = 202469, upload-time = "2026-03-12T09:01:13.38Z" }, -] + +[package.metadata] +requires-dist = [ + { name = "anthropic", marker = "extra == 'test'", specifier = ">=0.72" }, + { name = "claude-agent-sdk", marker = "extra == 'test'" }, + { name = "coverage", marker = "extra == 'test'" }, + { name = "distro", specifier = ">=1.5.0" }, + { name = "django", marker = "extra == 'test'", specifier = ">=5.2.15,<6.0" }, + { name = "django-stubs", marker = "extra == 'dev'" }, + { name = "fastmcp", marker = "extra == 'test'", specifier = ">=2.0" }, + { name = "freezegun", marker = "extra == 'test'", specifier = "==1.5.1" }, + { name = "gevent", marker = "implementation_name == 'cpython' and extra == 'test'", specifier = ">=25.4.1" }, + { name = "google-genai", marker = "extra == 'test'" }, + { name = "griffe", marker = "extra == 'dev'" }, + { name = "httpx", marker = "extra == 'async'", specifier = ">=0.27.0,<1.0" }, + { name = "jsonschema", marker = "extra == 'test'", specifier = ">=4.0" }, + { name = "langchain", marker = "extra == 'langchain'", specifier = ">=1.3.9" }, + { name = "langchain", marker = "extra == 'test'", specifier = ">=1.3.9" }, + { name = "langchain-anthropic", marker = "extra == 'test'", specifier = ">=1.0" }, + { name = "langchain-community", marker = "extra == 'test'", specifier = ">=0.4" }, + { name = "langchain-core", marker = "extra == 'test'", specifier = ">=1.0" }, + { name = "langchain-openai", marker = "extra == 'test'", specifier = ">=1.1.14" }, + { name = "langgraph", marker = "extra == 'test'", specifier = ">=1.0" }, + { name = "langgraph-checkpoint", marker = "extra == 'test'", specifier = ">=4.1.1" }, + { name = "lxml", marker = "extra == 'dev'" }, + { name = "mcp", marker = "extra == 'test'", specifier = ">=1.28.1,<2" }, + { 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 == 'otel'", specifier = ">=1.20.0" }, + { name = "opentelemetry-exporter-otlp-proto-http", marker = "extra == 'test'", specifier = ">=1.20.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'" }, + { name = "parameterized", marker = "extra == 'test'", specifier = ">=0.8.1" }, + { name = "pre-commit", marker = "extra == 'dev'" }, + { name = "pydantic", marker = "extra == 'dev'", specifier = ">=2.12.0" }, + { name = "pydantic", marker = "extra == 'test'", specifier = ">=2.12.0" }, + { name = "pytest", marker = "extra == 'test'" }, + { name = "pytest-asyncio", marker = "extra == 'test'" }, + { name = "pytest-bdd", marker = "extra == 'test'", specifier = ">=8.1.0" }, + { name = "pytest-timeout", marker = "extra == 'test'" }, + { name = "python-dateutil", marker = "extra == 'test'", specifier = ">=2.9.0.post0" }, + { name = "requests", specifier = ">=2.7,<3.0" }, + { name = "ruff", marker = "extra == 'dev'" }, + { name = "setuptools", marker = "extra == 'dev'" }, + { name = "tiktoken", marker = "extra == 'test'", specifier = ">=0.12.0" }, + { name = "tomli", marker = "extra == 'dev'" }, + { name = "tomli-w", marker = "extra == 'dev'" }, + { name = "twine", marker = "extra == 'dev'" }, + { name = "types-requests", marker = "extra == 'dev'" }, + { name = "types-setuptools", marker = "extra == 'dev'" }, + { name = "typing-extensions", specifier = ">=4.2.0" }, + { name = "tzdata", marker = "extra == 'test'" }, + { name = "wheel", marker = "extra == 'dev'" }, + { 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"] + +[package.metadata.requires-dev] +dev = [{ name = "claude-agent-sdk", specifier = ">=0.1.50" }] [[package]] name = "propcache" @@ -1454,18 +1500,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" }, ] -[[package]] -name = "python-dateutil" -version = "2.9.0.post0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "six" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, -] - [[package]] name = "python-dotenv" version = "1.2.3" @@ -1833,15 +1867,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" }, ] -[[package]] -name = "six" -version = "1.17.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, -] - [[package]] name = "sniffio" version = "1.3.1" diff --git a/examples/example-ai-openai-agents/pyproject.toml b/examples/example-ai-openai-agents/pyproject.toml index 647400643..06ad91cd7 100644 --- a/examples/example-ai-openai-agents/pyproject.toml +++ b/examples/example-ai-openai-agents/pyproject.toml @@ -3,6 +3,9 @@ name = "example-ai-openai-agents" version = "0.1.0" requires-python = ">=3.10" dependencies = [ - "posthog==7.9.12", + "posthog", "openai-agents==0.13.1", ] + +[tool.uv.sources] +posthog = { path = "../.." } diff --git a/examples/example-ai-openai-agents/uv.lock b/examples/example-ai-openai-agents/uv.lock index 63ad72f88..0a084773f 100644 --- a/examples/example-ai-openai-agents/uv.lock +++ b/examples/example-ai-openai-agents/uv.lock @@ -9,7 +9,7 @@ resolution-markers = [ ] [options] -exclude-newer = "0001-01-01T00:00:00Z" # This has no effect and is included for backwards compatibility when using relative exclude-newer values. +exclude-newer = "2026-10-03T03:16:09.242998Z" exclude-newer-span = "P7D" [[package]] @@ -44,15 +44,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309", size = 67548, upload-time = "2026-03-19T14:22:23.645Z" }, ] -[[package]] -name = "backoff" -version = "2.2.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/47/d7/5bbeb12c44d7c4f2fb5b56abce497eb5ed9f34d85701de869acedd602619/backoff-2.2.1.tar.gz", hash = "sha256:03f829f5bb1923180821643f8753b0502c3b682293992485b0eef2807afa5cba", size = 17001, upload-time = "2022-10-05T19:19:32.061Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/df/73/b6e24bd22e6720ca8ee9a85a0c4a2971af8497d8f3193fa05390cbd46e09/backoff-2.2.1-py3-none-any.whl", hash = "sha256:63579f9a0628e06278f7e47b7d7d5b6ce20dc65c5e96a6f3ca99a6adca0396e8", size = 15148, upload-time = "2022-10-05T19:19:30.546Z" }, -] - [[package]] name = "certifi" version = "2026.2.25" @@ -376,7 +367,7 @@ dependencies = [ [package.metadata] requires-dist = [ { name = "openai-agents", specifier = "==0.13.1" }, - { name = "posthog", specifier = "==7.9.12" }, + { name = "posthog", directory = "../../" }, ] [[package]] @@ -384,7 +375,7 @@ name = "exceptiongroup" version = "1.3.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "typing-extensions" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" } wheels = [ @@ -646,20 +637,75 @@ wheels = [ [[package]] name = "posthog" -version = "7.9.12" -source = { registry = "https://pypi.org/simple" } +version = "7.64.1" +source = { directory = "../../" } dependencies = [ - { name = "backoff" }, { name = "distro" }, - { name = "python-dateutil" }, { name = "requests" }, - { name = "six" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/1c/a7/2865487853061fbd62383492237b546d2d8f7c1846272350d2b9e14138cd/posthog-7.9.12.tar.gz", hash = "sha256:ebabf2eb2e1c1fbf22b0759df4644623fa43cc6c9dcbe9fd429b7937d14251ec", size = 176828, upload-time = "2026-03-12T09:01:15.184Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/65/a9/7a803aed5a5649cf78ea7b31e90d0080181ba21f739243e1741a1e607f1f/posthog-7.9.12-py3-none-any.whl", hash = "sha256:7175bd1698a566bfea98a016c64e3456399f8046aeeca8f1d04ae5bf6c5a38d0", size = 202469, upload-time = "2026-03-12T09:01:13.38Z" }, -] + +[package.metadata] +requires-dist = [ + { name = "anthropic", marker = "extra == 'test'", specifier = ">=0.72" }, + { name = "claude-agent-sdk", marker = "extra == 'test'" }, + { name = "coverage", marker = "extra == 'test'" }, + { name = "distro", specifier = ">=1.5.0" }, + { name = "django", marker = "extra == 'test'", specifier = ">=5.2.15,<6.0" }, + { name = "django-stubs", marker = "extra == 'dev'" }, + { name = "fastmcp", marker = "extra == 'test'", specifier = ">=2.0" }, + { name = "freezegun", marker = "extra == 'test'", specifier = "==1.5.1" }, + { name = "gevent", marker = "implementation_name == 'cpython' and extra == 'test'", specifier = ">=25.4.1" }, + { name = "google-genai", marker = "extra == 'test'" }, + { name = "griffe", marker = "extra == 'dev'" }, + { name = "httpx", marker = "extra == 'async'", specifier = ">=0.27.0,<1.0" }, + { name = "jsonschema", marker = "extra == 'test'", specifier = ">=4.0" }, + { name = "langchain", marker = "extra == 'langchain'", specifier = ">=1.3.9" }, + { name = "langchain", marker = "extra == 'test'", specifier = ">=1.3.9" }, + { name = "langchain-anthropic", marker = "extra == 'test'", specifier = ">=1.0" }, + { name = "langchain-community", marker = "extra == 'test'", specifier = ">=0.4" }, + { name = "langchain-core", marker = "extra == 'test'", specifier = ">=1.0" }, + { name = "langchain-openai", marker = "extra == 'test'", specifier = ">=1.1.14" }, + { name = "langgraph", marker = "extra == 'test'", specifier = ">=1.0" }, + { name = "langgraph-checkpoint", marker = "extra == 'test'", specifier = ">=4.1.1" }, + { name = "lxml", marker = "extra == 'dev'" }, + { name = "mcp", marker = "extra == 'test'", specifier = ">=1.28.1,<2" }, + { 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 == 'otel'", specifier = ">=1.20.0" }, + { name = "opentelemetry-exporter-otlp-proto-http", marker = "extra == 'test'", specifier = ">=1.20.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'" }, + { name = "parameterized", marker = "extra == 'test'", specifier = ">=0.8.1" }, + { name = "pre-commit", marker = "extra == 'dev'" }, + { name = "pydantic", marker = "extra == 'dev'", specifier = ">=2.12.0" }, + { name = "pydantic", marker = "extra == 'test'", specifier = ">=2.12.0" }, + { name = "pytest", marker = "extra == 'test'" }, + { name = "pytest-asyncio", marker = "extra == 'test'" }, + { name = "pytest-bdd", marker = "extra == 'test'", specifier = ">=8.1.0" }, + { name = "pytest-timeout", marker = "extra == 'test'" }, + { name = "python-dateutil", marker = "extra == 'test'", specifier = ">=2.9.0.post0" }, + { name = "requests", specifier = ">=2.7,<3.0" }, + { name = "ruff", marker = "extra == 'dev'" }, + { name = "setuptools", marker = "extra == 'dev'" }, + { name = "tiktoken", marker = "extra == 'test'", specifier = ">=0.12.0" }, + { name = "tomli", marker = "extra == 'dev'" }, + { name = "tomli-w", marker = "extra == 'dev'" }, + { name = "twine", marker = "extra == 'dev'" }, + { name = "types-requests", marker = "extra == 'dev'" }, + { name = "types-setuptools", marker = "extra == 'dev'" }, + { name = "typing-extensions", specifier = ">=4.2.0" }, + { name = "tzdata", marker = "extra == 'test'" }, + { name = "wheel", marker = "extra == 'dev'" }, + { 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"] + +[package.metadata.requires-dev] +dev = [{ name = "claude-agent-sdk", specifier = ">=0.1.50" }] [[package]] name = "pycparser" @@ -834,18 +880,6 @@ crypto = [ { name = "cryptography" }, ] -[[package]] -name = "python-dateutil" -version = "2.9.0.post0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "six" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, -] - [[package]] name = "python-dotenv" version = "1.2.3" @@ -1037,15 +1071,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d1/b7/b95708304cd49b7b6f82fdd039f1748b66ec2b21d6a45180910802f1abf1/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:ac37f9f516c51e5753f27dfdef11a88330f04de2d564be3991384b2f3535d02e", size = 562191, upload-time = "2025-11-30T20:24:36.853Z" }, ] -[[package]] -name = "six" -version = "1.17.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, -] - [[package]] name = "sniffio" version = "1.3.1" diff --git a/openfeature-provider/pyproject.toml b/openfeature-provider/pyproject.toml index 4987c5008..2fb16aaac 100644 --- a/openfeature-provider/pyproject.toml +++ b/openfeature-provider/pyproject.toml @@ -27,7 +27,7 @@ dependencies = [ # single-call API. Pinned to the posthog major this provider is released and # tested against (both ship from this repo); bump the upper bound when moving # the provider to a new posthog major. - "posthog>=7.0.0,<8.0.0", + "posthog>=7.0.0,<9.0.0", "openfeature-sdk>=0.8.0", ] diff --git a/posthog/capture_send.py b/posthog/capture_send.py index 3c5f916d7..eabf343be 100644 --- a/posthog/capture_send.py +++ b/posthog/capture_send.py @@ -91,7 +91,7 @@ class CaptureEventResult: ``result`` is ``ok``, ``warning``, ``drop`` or ``retry``, or a value newer than this SDK, which counts as success. ``details`` is the server's reason - tag, such as ``billing_limit_exceeded``. + tag, such as ``llm_events_over_quota``. """ result: Optional[str] @@ -156,7 +156,7 @@ def __init__( def verdict_summary(self) -> str: """Count the undelivered events by verdict and reason, for example - ``drop/billing_limit_exceeded=2, retry/not_persisted=1``. + ``drop/llm_events_over_quota=2, retry/not_persisted=1``. Counts the events in ``drops`` and ``retry_exhausted``: events the server dropped, and events still pending retry after the last 2xx diff --git a/posthog/test/test_capture_send.py b/posthog/test/test_capture_send.py index 1f8c95062..9d87fa5b1 100644 --- a/posthog/test/test_capture_send.py +++ b/posthog/test/test_capture_send.py @@ -366,13 +366,15 @@ def test_drop_accumulated_across_attempts_surfaces_on_later_success(self) -> Non stub, exc = self._run_expecting_error( batch, [ - _results_response({"u-drop": ("drop", "billing"), "u-retry": "retry"}), + _results_response( + {"u-drop": ("drop", "llm_events_over_quota"), "u-retry": "retry"} + ), _results_response({"u-retry": "ok"}), ], ) self.assertEqual(len(stub.calls), 2) self.assertEqual(stub.calls[1]["uuids"], ["u-retry"]) # only retry resent - self.assertEqual(exc.drops, [("u-drop", "billing")]) + self.assertEqual(exc.drops, [("u-drop", "llm_events_over_quota")]) self.assertEqual(exc.retry_exhausted, []) def test_retry_exhausted_raises_with_uuids(self) -> None: @@ -394,7 +396,7 @@ def test_retry_exhausted_carries_earlier_drops(self) -> None: _results_response( { "u-ok": "ok", - "u-drop": ("drop", "billing"), + "u-drop": ("drop", "llm_events_over_quota"), "u-retry": "retry", } ), @@ -405,16 +407,18 @@ def test_retry_exhausted_carries_earlier_drops(self) -> None: self.assertEqual(len(stub.calls), 2) self.assertEqual(exc.endpoint, _CAPTURE_V1_PATH) self.assertEqual(exc.retry_exhausted, ["u-retry"]) - self.assertEqual(exc.drops, [("u-drop", "billing")]) + self.assertEqual(exc.drops, [("u-drop", "llm_events_over_quota")]) self.assertEqual( exc.event_results, { "u-ok": CaptureEventResult("ok"), - "u-drop": CaptureEventResult("drop", "billing"), + "u-drop": CaptureEventResult("drop", "llm_events_over_quota"), "u-retry": CaptureEventResult("retry", "not_persisted"), }, ) - self.assertEqual(exc.verdict_summary(), "drop/billing=1, retry/not_persisted=1") + self.assertEqual( + exc.verdict_summary(), "drop/llm_events_over_quota=1, retry/not_persisted=1" + ) @parameterized.expand( [ @@ -432,11 +436,11 @@ def test_results_match_non_canonical_uuid(self, _name, sent_uuid) -> None: [_msg(sent_uuid)], [ _results_response({canonical: "retry"}), - _results_response({canonical: ("drop", "billing")}), + _results_response({canonical: ("drop", "llm_events_over_quota")}), ], ) self.assertEqual(len(stub.calls), 2) - self.assertEqual(exc.drops, [(canonical, "billing")]) + self.assertEqual(exc.drops, [(canonical, "llm_events_over_quota")]) def test_malformed_2xx_is_terminal(self) -> None: stub, exc = self._run_expecting_error( @@ -536,17 +540,17 @@ class TestCaptureLossMessage(unittest.TestCase): 200, "2 event(s) not delivered", endpoint=_CAPTURE_V1_PATH, - drops=[("u-drop", "billing")], + drops=[("u-drop", "llm_events_over_quota")], retry_exhausted=["u-retry"], event_results={ "u-ok": CaptureEventResult("ok"), - "u-drop": CaptureEventResult("drop", "billing"), + "u-drop": CaptureEventResult("drop", "llm_events_over_quota"), "u-retry": CaptureEventResult("retry", "not_persisted"), }, ), 3, "2 event(s) not persisted by /i/v1/analytics/events: 1 dropped, " - "1 out of retries (drop/billing=1, retry/not_persisted=1)", + "1 out of retries (drop/llm_events_over_quota=1, retry/not_persisted=1)", ), ( "request_failure_after_partial_success",