NewYour coding agent can read the release notes before it upgrades.Set up the MCP server →
PyPI · #3092 most downloaded on PyPI
Powerful Feature flagging and A/B testing for Python apps
Last release 26 days ago
08 Sep 2026
Ships fairly regularly
a new release about every 2 weeks
Nearly every release is documented
notes for 36 of 37 stable releases
Nothing withdrawn
no release was ever pulled
5 years old
37 releases · first in 2021
One column per quarter.
empty variations or short meta in a rule crashed evaluation with IndexError - Merge pull request #137 from growthbook/fix/empty-variations-index-error
The SDK now runs contextual bandit experiments: GrowthBook trains per-segment variation weights server-side, and the SDK routes each user to their mat
The SDK now runs contextual bandit experiments: GrowthBook trains per-segment variation weights server-side, and the SDK routes each user to their matching leaf and buckets with those weights — no client-side model. Works with encrypted payloads and the new set_payload() on both clients. Older SDKs safely serve the feature's default value.
Servers can now buffer exposures and forward them to a client SDK, which fires them in the browser via setDeferredTrackingCalls() + fireDeferredTrackingCalls() (JS SDK 1.7.0+ for the forwarded user context).
Opt in with GrowthBook(defer_tracking=True) + get_deferred_tracking_calls(), or pass a per-request TrackingBuffer to the
async client's eval methods. Buffering is independent of on_experiment_viewed, and entries are always JSON-ready.
on_experiment_viewed — previously those exposures were silently dropped. on_feature_usage fires for every feature an evaluation touches, and the sync client's subscriptions see prerequisite experiments too.Experiment.to_dict() preserves an explicit coverage of 0.See the CHANGELOG
for the full list, including behavioral details and a performance note.
Contextual bandit support in both clients, at behavioral parity with the JavaScript SDK:
contextualBandits payload section (and encryptedContextualBandits), evaluates contextualBanditRef/contextualVariations rules with per-leaf weight substitution, and reports leafId, variationWeights, and banditVersion on experiment results for exposure logging.set_payload() on GrowthBook and GrowthBookClient for seeding full SDK payloads; only the sections present are overwritten, and encrypted sections are decrypted with the configured decryption_key (JS setPayload semantics).savedGroups, contextualBandits) preserve the previous value at every layer instead of wiping it; the synchronous client serializes payload writers and publishes one coherent evaluation snapshot per update (evaluations stay lock-free), matching the async client.variationWeights always match the weights bucketing actually used, and a single, total validity rule governs every weight vector: getBucketRanges normalizes vectors with negative, non-finite, boolean, non-numeric, or float-overflowing entries (not just wrong length/sum) to equal weights — never raising, even on arbitrary-precision integers, so bucket ranges can never be inverted, and bandit leaf/aggregate/override propensities always describe the vector actually used. Bandit identifiers get the same treatment: a leaf with a non-integer leafId is malformed (aggregate-weights fallback), and a non-integer banditVersion is omitted, so exposure metadata never carries invalid attribution ids into bandit training. A rule that pairs contextualBanditRef with explicit ranges buckets on the ranges (unchanged, matching the JS SDK) but drops the bandit metadata, since leaf propensities cannot describe a ranges-governed assignment.rule.tracks results from the proxy keep leafId, variationWeights, and banditVersion when replayed through the tracking callback, held to the same validity rules as locally evaluated exposures (invalid identifiers or weight vectors are dropped, not forwarded).UserContext mid-flight cannot change leaf routing, remote payloads, or forced assignments. Plain CDN evaluations never yield and skip the copy entirely; deferred callbacks get fire-time snapshots in both clients. preload_remote_eval takes the same call-time snapshot, so mutating the context after preloading can no longer cache one attribute state's response under another's key via the stale-while-revalidate background refresh.Deferred tracking: buffer experiment exposures for forwarding to a client SDK (which fires them via setDeferredTrackingCalls + fireDeferredTrackingCalls). Opt-in and independent of on_experiment_viewed — the buffer records first, the callback still fires. Entries use the JS SDK's TrackingData shape, deduped per unique assignment and JSON round-tripped at exposure time, so buffered payloads are always json.dumps-ready — an exposure carrying a non-JSON value (e.g. a datetime attribute) is dropped and logged, never the batch. Firing the forwarded user context on the receiving side requires JS SDK 1.7.0+.
GrowthBook(defer_tracking=True) with get_deferred_tracking_calls() / clear_deferred_tracking_calls().TrackingBuffer to eval_feature / run / is_on / is_off / get_feature_value and read it with buffer.get_calls(); the caller owns the buffer, so concurrent requests never mix exposures.prerequisite exposure telemetry and deferred tracking (4dac52f)
tests/scripts/benchmark_eval_overhead.py (500k sequential in-memory evals, best of 5): the default-value path costs ~0.13 microseconds more per evaluation than 3.0 (~1.29 -> ~1.42 us/eval, ~10%); a realistic experiment-rule path is within ~1% (~6.3 us/eval). The residual is the EvaluationContext carrying the callback fields — the mechanism that fixes silently-dropped prerequisite exposures — and is accepted deliberately; usage bookkeeping and the deprecated-kwarg shim are already skipped entirely when unused.eval_prereqs dropped every callback, so those exposures were silently lost. Fixed structurally by carrying tracking_cb / feature_usage_cb / callback_subscription on the EvaluationContext (like the JS SDK's global context) so nested evaluations inherit them. As a result:
on_experiment_viewed fires for prerequisite experiment assignments (including when a gate ultimately fails, matching the JS SDK).on_feature_usage fires for every feature evaluated — prerequisites included — once per key per evaluation (previously it fired only for the top-level key, on every call, with no dedupe).subscribe() callbacks see prerequisite experiments, and its get_all_results() includes them. The async client's subscriptions still fire only from run() — like the JS multi-user client, it has no per-user assignment change-detection, so per-eval firing would repeat every subscriber callback on every request.core.eval_feature / core.run_experiment now read callbacks from the EvaluationContext; the old tracking_cb / callback_subscription keyword arguments still work but are deprecated: they are installed on the context only for the duration of that call (prerequisites inherit them) and the previous context fields are restored afterwards, matching their pre-3.1 invocation-scoped behavior.Experiment.to_dict() no longer coerces an explicit coverage of 0 to 1.savedGroups from a feature refresh were applied to the evaluation context one refresh late in the synchronous client.snake_case aliases for remaining camelCase APIs; camelCase marked @deprecated (still works, warns).
typing_extensions>=4.5.GrowthBookClient calls tracking callbacks with keyword arguments — parameters must be named experiment, result, user_context.growthbook.PoolManager).python -m growthbook.codegen turns your features JSON into a client with checked feature keys and per-key value types. Supports encrypted payloads via --decryption-key.@deprecated (still works, warns).No runtime behavior changes beyond the callback invocation above. Docs: [Type Safety section](https://docs.growthbook.io/lib/python#type-safety).
Full Changelog: v2.4.0...v3.0.0
Async sticky bucketing for GrowthBookClient ( #128 , #129 ) by @madhuchavva :
GrowthBookClient (#128, #129) by @madhuchavva:
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.UserContext, matching the JavaScript SDK's multi-user client; concurrent evaluations for the same user share a single cancellation-safe in-flight lookup.GrowthBookClient.flush_sticky_bucket_saves() waits for all pending writes to persist (useful for serverless and short-lived processes); close() flushes automatically.Options(sticky_bucket_cache_ttl=..., sticky_bucket_cache_size=...); disabled by default.GrowthBookClient (#128, #129) by @madhuchavva: 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.customFields property to Experiment (#125) by @vazarkevychGrowthBookClient: feature updates now swap an immutable snapshot instead of taking a per-evaluation lock (#129) by @madhuchavvastop_refresh() no longer blocks the event loop during shutdown (#128) by @madhuchavvatests/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.GrowthBook class now raises ValueError at construction if given an async sticky bucket service, instead of failing silently at runtime (#128) by @madhuchavvaGrowthBookClient 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.Full Changelog: v2.3.1...v2.4.0
SDK Conformance Audit - Cases.json, evalCondition operator audit and partial feature-eval / experiment-assignment by @madhuchavva in #122
$eq and direct equality no longer coerce across types, so values like 5 and "5" or true and 1 no longer match. (7f9d2d2)$ne now returns the inverse of strict equality for these cases.$eq. (a8ff302, 7f9d2d)NaN comparison handling so NaN does not compare equal to itself and ordered comparisons involving NaN evaluate as false. (672136a)$ini, $nini, and $alli. (5f45087)hashVersion: 2, and sticky-bucket bucket-version boundaries.There are no API changes, but this release can change feature targeting results for customers whose conditions relied on Python’s previous coercive equality behavior.
Full Changelog: v2.3.0...v2.3.1
RemoteEval for Sync and Async Clients including cache invalidation, concurrent request coalescing, and cancellation handling by @madhuchavva in #118
preload_remote_eval() for async clients and support for proxy-provided rule.tracks tracking events by @madhuchavva in #118$ne, $notRegex, and $notRegexi condition behavior for incompatible inputs. (26d549b)Full Changelog: v2.2.2...v2.3.0
preserve version comparison normalization
decrypt SSE encrypted features payload and fix cache key
add log_event and set_event_logger to GrowthBook and GrowthBookClient
Add optional timeout for PoolManager
Fixes for process hanging and shutdown errors - Merge pull request #103 from growthbook/pr102
Supporting Dict Subclasses in Evaluation - Merge pull request #99 from growthbook/feat/isInstanceTypeCheck
Disabled features not being removed from cache
Case insensitive membership operators by @madhuchavva in https://github.com/growthbook/growthbook-python/pull/96
Full Changelog: https://github.com/growthbook/growthbook-python/compare/v2.1.0...v2.1.1
$ini, $nini, $alli
$ini: Case-insensitive version of $in operator$nini: Case-insensitive version of $nin operator$alli: Case-insensitive version of $all operator (0e26f7d)Adds support for regexi and $notRegexi - Case insensitive regex
Fixes for Async wrapper execution and other enhancements
Add user agent suffix optional prop - Merge pull request #87 from growthbook/fix/fetch-metadata
Add gzip encoding header to features call - Merge pull request #83 from growthbook/feat/etag-cache
Handle ETags natively for both sync & async clients - Merge pull request #81 from growthbook/feat/etag-cache
Type checks & Other enhancements - Merge pull request #77 from growthbook/feat/enhancements
Type checks & Other enhancements - Merge pull request #77 from growthbook/feat/enhancements (ea1567a)
Added skip_all_experiments to UserContext - enables per-user control over experiments. Closes #76
qa_mode to True globally for QA/staging environmentsskip_all_experiments to True per-user for specific usersAdded Type safety - 200+ type hints, mypy configurations - compliant with PEP 561
# MyPy catches these at dev time, not runtime:
gb = GrowthBook(qa_mode="true") #Err: Expected bool, got str
gb = GrowthBook(attributes=[]) # Err: Expected Dict, got List
bug fixes and tracking enhancements
## 1.4.5 (2025-10-08) ### Bug Fixes * Add FeatureUsageCallback
Added Background Refresh task for Features by @madhuchavva in https://github.com/growthbook/growthbook-python/pull/66
Full Changelog: https://github.com/growthbook/growthbook-python/compare/v1.4.3...v1.4.4
## 1.4.3 (2025-09-19) ### Bug Fixes * Fixes for graceful shutdown
Keep the Socket open with a configurable connection timeout (84284d)
## 1.4.1 (2025-09-12) ### Bug Fixes * add timeout to SSE client
Tracking Plugins Compatibility with Async & Sync Clients
## 1.3.1 (2025-06-13) ### Bug Fixes * Tracking linked experiments
Feat/auto release by @madhuchavva in https://github.com/growthbook/growthbook-python/pull/31
Full Changelog: https://github.com/growthbook/growthbook-python/compare/v...v1.3.0
Fix zero value evaluation for _getOrigHashValue
All notable changes to this project will be documented in this file. See standard-version for commit guidelines.
Support for prerequisite feature flags
Update to the official 0.4.1 GrowthBook SDK spec version
Return featureId as part of the experiment result
featureId as part of the experiment resultNone (now it will skip the experiment as expected)Don't skip feature rules when experiment variation is forced
- Support for Feature Flags
Initial release (inline experiments only)
Nothing published for this version
Your coding agent can read these notes before it upgrades. Set up the MCP server →