Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
".": "2.3.1"
".": "2.4.0"
}
41 changes: 41 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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)


Expand Down
2 changes: 1 addition & 1 deletion growthbook/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,5 @@
)

# x-release-please-start-version
__version__ = "2.3.1"
__version__ = "2.4.0"
# x-release-please-end
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[bumpversion]
current_version = 2.3.1
current_version = 2.4.0
commit = True
tag = True

Expand Down
Loading