Powerful Feature flagging and A/B testing for Python apps
Project description
GrowthBook Python SDK
Powerful Feature flagging and A/B testing for Python apps.
- No external dependencies
- Lightweight and fast
- No HTTP requests everything is defined and evaluated locally
- Python 3.6+
- 100% test coverage
- Flexible targeting
- Use your existing event tracking (GA, Segment, Mixpanel, custom)
- Remote configuration to change feature flags without deploying new code
Installation
pip install growthbook
(recommended) or copy growthbook.py
into your project
Quick Usage
import requests
from growthbook import GrowthBook
# We recommend using a db or cache layer in production
apiResp = requests.get("https://cdn.growthbook.io/api/features/MY_API_KEY")
features = apiResp.json()["features"]
# User attributes for targeting and experimentation
attributes = {
"id": "123",
"customUserAttribute": "foo"
}
def on_experiment_viewed(experiment, result):
# Use whatever event tracking system you want
analytics.track(attributes["id"], "Experiment Viewed", {
'experimentId': experiment.key,
'variationId': result.variationId
})
# Create a GrowthBook instance
gb = GrowthBook(
attributes = attributes,
features = features,
trackingCallback = on_experiment_viewed
)
# Simple on/off feature gating
if gb.isOn("my-feature"):
print("My feature is on!")
# Get the value of a feature with a fallback
color = gb.getFeatureValue("button-color-feature", "blue")
GrowthBook class
The GrowthBook constructor has the following parameters:
- enabled (
bool
) - Flag to globally disable all experiments. Default true. - attributes (
dict
) - Dictionary of user attributes that are used for targeting and to assign variations - url (
str
) - The URL of the current request (if applicable) - features (
dict
) - Feature definitions from the GrowthBook API - forcedVariations (
dict
) - Dictionary of forced experiment variations (used for QA) - qaMode (
boolean
) - If true, random assignment is disabled and only explicitly forced variations are used. - trackingCallback (
callable
) - A function that takesexperiment
andresult
as arguments.
There are also getter and setter methods for features and attributes if you need to update them later in the request:
gb.setFeatures(gb.getFeatures())
gb.setAttributes(gb.getAttributes())
Features
Defines all of the available features plus rules for how to assign values to users. Features are usually fetched from the GrowthBook API and persisted in cache or a database in production.
Feature definitions are defined in a JSON format. You can fetch them directly from the GrowthBook API:
import requests
apiResp = requests.get("https://cdn.growthbook.io/api/features/MY_API_KEY")
features = apiResp.json()["features"]
Or, you can use a copy stored in your database or cache server instead:
import json
json_string = '{"feature-1":{...},"feature-2":{...},...}'
features = json.loads(json_string)
We recommend using the db/cache approach for production.
Attributes
You can specify attributes about the current user and request. These are used for two things:
- Feature targeting (e.g. paid users get one value, free users get another)
- Assigning persistent variations in A/B tests (e.g. user id "123" always gets variation B)
Attributes can be any JSON data type - boolean, integer, float, string, list, or dict.
attributes = {
'id': "123",
'loggedIn': True,
'age': 21.5,
'tags': ["tag1", "tag2"],
'account': {
'age': 90
]
}
Tracking Experiments
Any time an experiment is run to determine the value of a feature, you want to track that event in your analytics system.
You can use the trackingCallback
option to do this:
from growthbook import GrowthBook, Experiment, Result
def on_experiment_viewed(experiment: Experiment, result: Result):
# Use whatever event tracking system you want
analytics.track(attributes["id"], "Experiment Viewed", {
'experimentId': experiment.key,
'variationId': result.variationId
})
gb = GrowthBook(
trackingCallback = on_experiment_viewed
)
Using Features
There are 3 main methods for interacting with features.
gb.isOn("feature-key")
returns true if the feature is ongb.isOff("feature-key")
returns false if the feature is ongb.getFeatureValue("feature-key", "default")
returns the value of the feature with a fallback
In addition, you can use gb.evalFeature("feature-key")
to get back a FeatureResult
object with the following properties:
- value - The JSON-decoded value of the feature (or
null
if not defined) - on and off - The JSON-decoded value cast to booleans
- source - Why the value was assigned to the user. One of
unknownFeature
,defaultValue
,force
, orexperiment
- experiment - Information about the experiment (if any) which was used to assign the value to the user
- experimentResult - The result of the experiment (if any) which was used to assign the value to the user
Inline Experiments
Instead of declaring all features up-front and referencing them by ids in your code, you can also just run an experiment directly. This is done with the gb->run
method:
from growthbook import Experiment
exp = Experiment(
key = "my-experiment",
variations = ["red", "blue", "green"]
)
# Either "red", "blue", or "green"
print(gb.run(exp).value)
As you can see, there are 2 required parameters for experiments, a string key, and an array of variations. Variations can be any data type, not just strings.
There are a number of additional settings to control the experiment behavior:
- key (
str
) - The globally unique tracking key for the experiment - variations (
any[]
) - The different variations to choose between - weights (
float[]
) - How to weight traffic between variations. Must add to 1. - coverage (
float
) - What percent of users should be included in the experiment (between 0 and 1, inclusive) - condition (
dict
) - Targeting conditions - force (
int
) - All users included in the experiment will be forced into the specified variation index - hashAttribute (
string
) - What user attribute should be used to assign variations (defaults to "id") - namespace (
tuple[str,float,float]
) - Used to run mutually exclusive experiments.
Here's an example that uses all of them:
exp = Experiment(
key="my-test",
# Variations can be a list of any data type
variations=[0, 1],
# Run a 40/60 experiment instead of the default even split (50/50)
weights=[0.4, 0.6],
# Only include 20% of users in the experiment
coverage=0.2,
# Targeting condition using a MongoDB-like syntax
condition={
'country': 'US',
'browser': {
'$in': ['chrome', 'firefox']
}
},
# Use an alternate attribute for assigning variations (default is 'id')
hashAttribute="sessionId",
# Includes the first 50% of users in the "pricing" namespace
# Another experiment with a non-overlapping range will be mutually exclusive (e.g. [0.5, 1])
namespace=("pricing", 0, 0.5),
)
Inline Experiment Return Value
A call to run
returns a Result
object with a few useful properties:
result = gb.run(exp)
# If user is part of the experiment
print(result.inExperiment) # True or False
# The index of the assigned variation
print(result.variationId) # e.g. 0 or 1
# The value of the assigned variation
print(result.value) # e.g. "A" or "B"
# If the variation was randomly assigned by hashing user attributes
print(result.hashUsed) # True or False
# The user attribute used to assign a variation
print(result.hashAttribute) # "id"
# The value of that attribute
print(result.hashValue) # e.g. "123"
The inExperiment
flag will be false if the user was excluded from being part of the experiment for any reason (e.g. failed targeting conditions).
The hashUsed
flag will only be true if the user was randomly assigned a variation. If the user was forced into a specific variation instead, this flag will be false.
Example Experiments
3-way experiment with uneven variation weights:
gb.run(Experiment(
key = "3-way-uneven",
variations = ["A","B","C"],
weights = [0.5, 0.25, 0.25]
))
Slow rollout (10% of users who match the targeting condition):
# User is marked as being in "qa" and "beta"
gb = GrowthBook(
attributes = {
"id": "123",
"beta": True,
"qa": True,
},
)
gb.run(Experiment(
key = "slow-rollout",
variations = ["A", "B"],
coverage = 0.1,
condition = {
'beta': True
}
))
Complex variations
result = gb.run(Experiment(
key = "complex-variations",
variations = [
("blue", "large"),
("green", "small")
],
))
# Either "blue,large" OR "green,small"
print(result.value[0] + "," + result.value[1])
Assign variations based on something other than user id
gb = GrowthBook(
attributes = {
"id": "123",
"company": "growthbook"
}
)
# Users in the same company will always get the same variation
gb.run(Experiment(
key = "by-company-id",
variations = ["A", "B"],
hashAttribute = "company"
))
Django
For Django (and other web frameworks), we recommend adding a simple middleware where you instantiate the GrowthBook object
from growthbook import GrowthBook
def growthbook_middleware(get_response):
def middleware(request):
request.gb = GrowthBook(
# ...
)
response = get_response(request)
request.gb.destroy() # Cleanup
return response
return middleware
Then, you can easily evaluate a feature (or run an inline experiment) in any of your views:
def index(request):
featureEnabled = request.gb.isOn("my-feature")
# ...
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Hashes for growthbook-0.3.0-py2.py3-none-any.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 88b5f7c908accc1bf2936c46bfd49c7564ac10247cba3f02eddfe7600477a262 |
|
MD5 | b4d8d3aae29767a5ca918f9524c5ca3a |
|
BLAKE2b-256 | 742016c8eefbfcda286b5a277808932724865a0c9697294e1609fe4f0a0d24e2 |