Skip to main content

Python SDK for SetBit feature flags and A/B testing

Project description

SetBit Python SDK

Official Python SDK for SetBit - Simple feature flags and A/B testing.

Features

  • Boolean Flags - Simple on/off feature toggles
  • 🧪 A/B Testing - Weighted variant distribution for experiments
  • 📊 Conversion Tracking - Track events and conversions
  • 🏷️ Tag-Based Targeting - Target by environment, app, team, region, etc.
  • 🚀 Fail-Open Design - Returns defaults if API is unreachable
  • 🪶 Lightweight - Minimal dependencies

Installation

pip install setbit

Quick Start

from setbit import SetBit

# Initialize the client
client = SetBit(
    api_key="pk_your_api_key_here",
    tags={"env": "production", "app": "web"}
)

# Check a boolean flag (user_id required for analytics)
user_id = get_current_user_id()
if client.enabled("new-checkout", user_id=user_id):
    show_new_checkout()
else:
    show_old_checkout()

# Rollout flag (gradual percentage-based rollout)
variant = client.variant("new-api", user_id=current_user.id)
if variant == "enabled":
    use_new_api()
else:
    use_old_api()

# Get A/B test variant
variant = client.variant("pricing-experiment", user_id=current_user.id)
if variant == "variant_a":
    show_price_99()
elif variant == "variant_b":
    show_price_149()
else:  # control
    show_price_129()

# Track conversions
client.track("purchase", user_id=current_user.id, metadata={"amount": 99.99})

API Reference

Initialization

SetBit(api_key: str, tags: dict = None, base_url: str = "https://flags.setbit.io")

Parameters:

  • api_key (str, required): Your SetBit API key
  • tags (dict, optional): Tags for targeting flags (e.g., {"env": "production", "app": "web"})
  • base_url (str, optional): API endpoint URL (useful for self-hosted instances)

Raises:

  • SetBitAuthError: If API key is invalid
  • SetBitAPIError: If initial flag fetch fails

Example:

client = SetBit(
    api_key="pk_abc123",
    tags={"env": "production", "app": "web", "region": "us-east"}
)

enabled(flag_name, user_id, default=False)

Check if a flag is enabled. Returns True if the flag is globally enabled, False otherwise.

Parameters:

  • flag_name (str): Name of the flag
  • user_id (str, required): User identifier (required for analytics and billing)
  • default (bool): Value to return if flag not found (default: False)

Returns: bool - True if enabled, False otherwise

Note: This method returns whether the flag is globally enabled. For rollout flags, use variant() to check which rollout group the user is in.

Example:

# Check if flag is enabled (user_id required)
user_id = get_current_user_id()
if client.enabled("new-dashboard", user_id=user_id):
    render_new_dashboard()
else:
    render_old_dashboard()

# With custom default
if client.enabled("beta-feature", user_id=user_id, default=True):
    show_beta_feature()

variant(flag_name, user_id, default="control")

Get the variant for an A/B test experiment or rollout flag.

Parameters:

  • flag_name (str): Name of the experiment or rollout flag
  • user_id (str): User identifier (required)
  • default (str): Variant to return if flag not found (default: "control")

Returns: str - Variant name

For experiments: "control", "variant_a", "variant_b", etc. For rollout flags: "enabled" (user in rollout) or "disabled" (user not in rollout)

Example:

# A/B test experiment
variant = client.variant("button-color-test", user_id=current_user.id)

if variant == "variant_a":
    button_color = "blue"
elif variant == "variant_b":
    button_color = "green"
else:  # control
    button_color = "red"

# Rollout flag (gradual percentage-based rollout)
variant = client.variant("new-api", user_id=current_user.id)

if variant == "enabled":
    use_new_api()  # User is in the rollout group
else:
    use_old_api()  # User is not in the rollout group

track(event_name, user_id, flag_name=None, variant=None, metadata=None)

Track a conversion event.

Parameters:

  • event_name (str): Name of the event (e.g., "purchase", "signup")
  • user_id (str, required): User identifier (required for analytics and billing)
  • flag_name (str, optional): Flag to associate with this conversion
  • variant (str, optional): Variant the user was assigned to (for A/B test attribution)
  • metadata (dict, optional): Additional event data

Returns: None

Note: This method fails silently - errors are logged but not raised.

Example:

user_id = get_current_user_id()

# Track conversion with variant attribution (recommended for A/B tests)
variant = client.variant("pricing-test", user_id=user_id)
# ... later when user converts ...
client.track(
    "purchase",
    user_id=user_id,
    flag_name="pricing-test",
    variant=variant,
    metadata={"amount": 99.99, "currency": "USD"}
)

# Track basic conversion
client.track("signup", user_id=user_id)

# Track with flag association only
client.track("purchase", user_id=user_id, flag_name="checkout-experiment")

refresh()

Manually refresh flags from the API.

Returns: None

Raises:

  • SetBitAuthError: If API key is invalid
  • SetBitAPIError: If API request fails

Example:

# Refresh flags if you know they've changed
client.refresh()

Usage Examples

Boolean Flags

from setbit import SetBit

client = SetBit(
    api_key="pk_abc123",
    tags={"env": "production", "app": "web"}
)

# Simple feature toggle (boolean flag) - user_id required
user_id = get_current_user_id()
if client.enabled("dark-mode", user_id=user_id):
    enable_dark_mode()

# With default value
debug_mode = client.enabled("debug-logging", user_id=user_id, default=False)

Rollout Flags

# Gradual percentage-based rollout
# Use variant() to check which rollout group the user is in
variant = client.variant("new-api-v2", user_id=current_user.id)

if variant == "enabled":
    use_api_v2()  # User is in the rollout group
else:
    use_api_v1()  # User is not in the rollout group

# Example: Rollout new checkout flow
checkout_variant = client.variant("new-checkout", user_id=current_user.id)

if checkout_variant == "enabled":
    render_new_checkout()
    client.track("checkout_started", user_id=current_user.id, flag_name="new-checkout")
else:
    render_old_checkout()

A/B Testing

# Get variant for experiment
variant = client.variant("homepage-hero", user_id=current_user.id)

if variant == "variant_a":
    # Version A: Large hero image
    render_hero(size="large", style="image")
elif variant == "variant_b":
    # Version B: Video hero
    render_hero(size="large", style="video")
elif variant == "variant_c":
    # Version C: Minimal hero
    render_hero(size="small", style="minimal")
else:
    # Control: Original hero
    render_hero(size="medium", style="image")

# Track conversion for this experiment (pass variant for proper attribution)
client.track("signup", user_id=current_user.id, flag_name="homepage-hero", variant=variant)

Conversion Tracking

user_id = get_current_user_id()

# Track page view
client.track("page_view", user_id=user_id, metadata={"page": "/pricing"})

# Track user signup
client.track("signup", user_id=user_id, metadata={
    "plan": "pro",
    "source": "landing_page"
})

# Track purchase with detailed metadata
client.track(
    "purchase",
    user_id=user_id,
    flag_name="checkout-experiment",
    metadata={
        "amount": 149.99,
        "currency": "USD",
        "items": 3,
        "payment_method": "credit_card"
    }
)

Error Handling

from setbit import SetBit, SetBitAuthError, SetBitAPIError

try:
    client = SetBit(api_key="invalid_key")
except SetBitAuthError:
    print("Invalid API key!")
    # Fall back to default behavior
    client = None

# Client returns safe defaults if initialization failed
user_id = get_current_user_id()
if client and client.enabled("new-feature", user_id=user_id):
    show_new_feature()
else:
    show_old_feature()

Multi-Environment Setup

import os
from setbit import SetBit

# Use environment variables for configuration
client = SetBit(
    api_key=os.getenv("SETBIT_API_KEY"),
    tags={
        "env": os.getenv("ENVIRONMENT", "development"),
        "app": "backend",
        "region": os.getenv("AWS_REGION", "us-east-1")
    }
)

# Same flag, different behavior per environment
# In dev: 100% rollout
# In prod: 10% gradual rollout
user_id = get_current_user_id()
if client.enabled("new-caching-layer", user_id=user_id):
    use_redis_cache()

Django Integration

# settings.py
from setbit import SetBit

SETBIT_CLIENT = SetBit(
    api_key=config("SETBIT_API_KEY"),
    tags={
        "env": config("ENVIRONMENT"),
        "app": "django-app"
    }
)

# views.py
from django.conf import settings

def checkout_view(request):
    user_id = str(request.user.id)
    variant = settings.SETBIT_CLIENT.variant("checkout-flow", user_id=user_id)

    if variant == "one_page":
        return render(request, "checkout_one_page.html")
    else:
        return render(request, "checkout_multi_step.html")

def purchase_complete(request):
    user_id = str(request.user.id)
    settings.SETBIT_CLIENT.track(
        "purchase",
        user_id=user_id,
        flag_name="checkout-flow",
        variant=request.POST.get("variant"),  # Pass variant from frontend
        metadata={"amount": request.POST["amount"]}
    )
    return redirect("thank_you")

Flask Integration

from flask import Flask, g
from setbit import SetBit

app = Flask(__name__)

@app.before_request
def setup_setbit():
    g.setbit = SetBit(
        api_key=app.config["SETBIT_API_KEY"],
        tags={"env": app.config["ENV"], "app": "flask-app"}
    )

@app.route("/")
def index():
    user_id = get_current_user_id()  # Your method to get user ID
    if g.setbit.enabled("new-homepage", user_id=user_id):
        return render_template("homepage_v2.html")
    return render_template("homepage.html")

Development

Running Tests

# Install dev dependencies
pip install -e ".[dev]"

# Run tests
pytest

# Run with coverage
pytest --cov=setbit --cov-report=html

Type Checking

mypy setbit

Code Formatting

black setbit tests
flake8 setbit tests

Error Handling

The SDK uses a fail-open philosophy - if something goes wrong, it returns safe default values rather than crashing your application.

Exception Types

  • SetBitError: Base exception for all SDK errors
  • SetBitAuthError: Invalid API key (raised during initialization/refresh)
  • SetBitAPIError: API request failed (raised during initialization/refresh)

Behavior

Method Error Behavior
__init__() Raises exception if API key invalid or flags can't be fetched
enabled() Returns default value if flag not found
variant() Returns default variant if flag not found
track() Logs error but does not raise exception
refresh() Raises exception if refresh fails

API Endpoints

The SDK communicates with these SetBit API endpoints:

  • GET /api/sdk/flags - Fetch flags for given tags
  • POST /api/events - Send conversion events

Requirements

  • Python >= 3.7
  • requests >= 2.25.0

Support

License

MIT License - see LICENSE file for details.

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add some amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

Made with ❤️ by the SetBit team

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

setbit-0.1.1.tar.gz (15.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

setbit-0.1.1-py3-none-any.whl (13.1 kB view details)

Uploaded Python 3

File details

Details for the file setbit-0.1.1.tar.gz.

File metadata

  • Download URL: setbit-0.1.1.tar.gz
  • Upload date:
  • Size: 15.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.8

File hashes

Hashes for setbit-0.1.1.tar.gz
Algorithm Hash digest
SHA256 068b52e063bb3a5cd94da7a6de3cd96c508e79d0b8b790a787fcbf98be334482
MD5 4c184ac5430a920d5ace31707d009459
BLAKE2b-256 44501b12454a0170f9070d028ab838b3366509f0667017839524ce126ee68ffd

See more details on using hashes here.

File details

Details for the file setbit-0.1.1-py3-none-any.whl.

File metadata

  • Download URL: setbit-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 13.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.8

File hashes

Hashes for setbit-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 c3a5e2603e5ca5572d3b977145bc244283ac5dd140b55bca941b89da93f37d26
MD5 1e76bd8869e84736f81c4f82e4458fa2
BLAKE2b-256 949be61501802c89de9f89995ad2e34698aca986b57f1ea3664fca90ef099107

See more details on using hashes here.

Supported by

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