Skip to main content

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.

Build Status

  • 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 takes experiment and result 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:

  1. Feature targeting (e.g. paid users get one value, free users get another)
  2. 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 on
  • gb.isOff("feature-key") returns false if the feature is on
  • gb.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, or experiment
  • 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"

# 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 is only set to True if the user was randomly assigned a variation. If the user failed any targeting rules or was forced into a specific variation, 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

growthbook-0.2.0.tar.gz (14.0 kB view hashes)

Uploaded Source

Built Distribution

growthbook-0.2.0-py2.py3-none-any.whl (15.2 kB view hashes)

Uploaded Python 2 Python 3

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page