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 keytags(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 invalidSetBitAPIError: 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 flaguser_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 flaguser_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 conversionvariant(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 invalidSetBitAPIError: 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 errorsSetBitAuthError: 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
- 📚 Documentation
- 💬 Discord Community
- 📧 Email: support@setbit.io
- 🐛 Report Issues
License
MIT License - see LICENSE file for details.
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add some amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
Made with ❤️ by the SetBit team
Project details
Release history Release notifications | RSS feed
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
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
068b52e063bb3a5cd94da7a6de3cd96c508e79d0b8b790a787fcbf98be334482
|
|
| MD5 |
4c184ac5430a920d5ace31707d009459
|
|
| BLAKE2b-256 |
44501b12454a0170f9070d028ab838b3366509f0667017839524ce126ee68ffd
|
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c3a5e2603e5ca5572d3b977145bc244283ac5dd140b55bca941b89da93f37d26
|
|
| MD5 |
1e76bd8869e84736f81c4f82e4458fa2
|
|
| BLAKE2b-256 |
949be61501802c89de9f89995ad2e34698aca986b57f1ea3664fca90ef099107
|