diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 23bd16f..0ab2e3a 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "2.3.1" + ".": "2.4.0" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c06c8c..906b4a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,46 @@ # Changelog +## [2.4.0](https://github.com/growthbook/growthbook-python/compare/v2.3.1...v2.4.0) (2026-08-18) + + +### Features + +* Async sticky bucketing for `GrowthBookClient` ([#128](https://github.com/growthbook/growthbook-python/pull/128), [#129](https://github.com/growthbook/growthbook-python/pull/129)): + * New `AbstractAsyncStickyBucketService` base class with `async` `get_assignments` / `save_assignments`; override `get_all_assignments` to batch all lookups for a user into one round trip (e.g. a single Redis `MGET`). Existing synchronous `AbstractStickyBucketService` implementations keep working with both clients — the async client offloads their blocking calls to a thread pool. + * Sticky bucket reads no longer block the event loop. Assignments are fetched per evaluation for the supplied `UserContext`, matching the JavaScript SDK's multi-user client; concurrent evaluations for the same user share a single cancellation-safe in-flight lookup. + * Sticky bucket writes are fire-and-forget: evaluation never waits on persistence. New assignments are immediately visible to later evaluations in the same process, and the client keeps an authoritative in-process copy of every document it has written, so a slow or stale store read can never roll back an assignment. Saves are serialized per document key so completion order cannot regress the stored document. + * New `GrowthBookClient.flush_sticky_bucket_saves()` waits for all pending writes to persist (useful for serverless and short-lived processes); `close()` flushes automatically. + * Optional bounded read cache for hot users via `Options(sticky_bucket_cache_ttl=..., sticky_bucket_cache_size=...)`; disabled by default. +* Async user callbacks in `GrowthBookClient` ([#128](https://github.com/growthbook/growthbook-python/pull/128), [#129](https://github.com/growthbook/growthbook-python/pull/129)): `on_experiment_viewed`, `on_feature_usage`, and `subscribe()` callbacks may now be coroutines. They are scheduled on the event loop without blocking evaluation, and a tracking callback that raises is retried on the next evaluation of the same experiment/user pair. +* Added `customFields` property to `Experiment` ([#125](https://github.com/growthbook/growthbook-python/pull/125)) + + +### Performance Improvements + +* Lock-free evaluation in `GrowthBookClient`: feature updates now swap an immutable snapshot instead of taking a per-evaluation lock ([#129](https://github.com/growthbook/growthbook-python/pull/129)) +* `stop_refresh()` no longer blocks the event loop during shutdown ([#128](https://github.com/growthbook/growthbook-python/pull/128)) +* In the included benchmark (`tests/scripts/benchmark_async_client.py`: 100 concurrent requests, 1 ms simulated service latency), distinct-user throughput with a network-backed sticky bucket service goes from ~350 evaluations/second with multi-second event-loop stalls on 2.3.x to ~20,000 evaluations/second with sub-2 ms loop lag. + + +### Bug Fixes + +* Sticky bucket refresh no longer triggers a redundant feature reload per identifier attribute in the synchronous client ([#124](https://github.com/growthbook/growthbook-python/pull/124)) +* The shared sticky bucket assignment-docs dict is now mutated in place instead of replaced when initially empty, preserving in-process read-your-writes ([#128](https://github.com/growthbook/growthbook-python/pull/128)) +* The synchronous `GrowthBook` class now raises `ValueError` at construction if given an async sticky bucket service, instead of failing silently at runtime ([#128](https://github.com/growthbook/growthbook-python/pull/128)) + + +### Tests and CI + +* Synced the conformance corpus with the JavaScript SDK 0.8.0 cases, with documented skips for unsupported contextual-bandit cases ([#130](https://github.com/growthbook/growthbook-python/pull/130)) +* Consumer-facing typing is now checked in CI via a mypy probe; public callback annotations accept both plain functions and coroutines ([#128](https://github.com/growthbook/growthbook-python/pull/128)) +* Replaced sleep-based concurrency tests with deterministic event-gated tests and added a high-concurrency benchmark harness for the async client ([#128](https://github.com/growthbook/growthbook-python/pull/128)) + + +### Compatibility notes + +* Sticky bucket writes from `GrowthBookClient` are now eventual rather than synchronous with evaluation. Read-your-writes is preserved in-process; short-lived processes should `await client.flush_sticky_bucket_saves()` (or `close()`) before exit to guarantee persistence. +* `GrowthBookClient` now fetches sticky bucket assignments per evaluation instead of caching them for the lifetime of the process. This matches the JavaScript SDK and picks up cross-process assignment changes promptly, but increases service lookups; opt into bounded caching with `sticky_bucket_cache_ttl` / `sticky_bucket_cache_size` if needed. + ## [2.3.1](https://github.com/growthbook/growthbook-python/compare/v2.3.0...v2.3.1) (2026-06-18) diff --git a/growthbook/__init__.py b/growthbook/__init__.py index 7b29090..fe81ab1 100644 --- a/growthbook/__init__.py +++ b/growthbook/__init__.py @@ -20,5 +20,5 @@ ) # x-release-please-start-version -__version__ = "2.3.1" +__version__ = "2.4.0" # x-release-please-end diff --git a/setup.cfg b/setup.cfg index 083bf98..fcb3c48 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 2.3.1 +current_version = 2.4.0 commit = True tag = True