Feature Toggles with Unleash in Python — Deep Dive
Feature toggles at scale introduce challenges that simple if/else checks do not reveal. Toggle evaluation must be fast and deterministic. Stale toggles accumulate technical debt. Testing all toggle combinations is combinatorially explosive. And the wrong default — failing open vs failing closed — can cause incidents. Production Unleash requires addressing each of these concerns.
SDK architecture
The Python Unleash SDK (UnleashClient) operates on a polling model:
- On initialization, the SDK fetches all toggle definitions from the Unleash server.
- Toggle definitions are cached in memory as a dictionary.
- A background thread polls for updates at a configurable interval (default 15 seconds).
is_enabled()evaluates strategies against the local cache — no network call.- A separate background thread sends usage metrics back to Unleash.
from UnleashClient import UnleashClient
client = UnleashClient(
url="http://unleash:4242/api",
app_name="order-service",
instance_id="pod-abc-123",
refresh_interval=10, # seconds between polls
metrics_interval=60, # seconds between metric reports
custom_headers={"Authorization": f"*:{api_token}"},
cache=FileCache("unleash_cache"), # Persist cache to survive restarts
)
client.initialize_client()
Failure modes
If the Unleash server is unreachable:
- On startup:
is_enabled()returns the fallback value (defaultFalse). - During operation: The SDK uses the last successfully fetched cache.
- With
FileCache: The SDK loads the persisted cache from disk, surviving full restarts during outages.
Always specify sensible defaults:
# Operational kill switch — default to enabled (feature ON)
if client.is_enabled("payments-enabled", fallback_function=lambda *a: True):
process_payment()
else:
return service_unavailable()
For kill switches, default to enabled. For new features, default to disabled. Getting this wrong means an Unleash outage either disables critical functionality or exposes unfinished features.
Custom strategies
Built-in strategies cover common cases. Custom strategies handle domain-specific logic:
from UnleashClient.strategies import Strategy
class SubscriptionStrategy(Strategy):
def load_provisioning(self):
return None
def apply(self, parameters, context):
allowed_plans = parameters.get("plans", "").split(",")
user_plan = context.get("properties", {}).get("plan", "")
return user_plan in allowed_plans
# Register the strategy
client = UnleashClient(
url="...",
app_name="order-service",
custom_strategies={"SubscriptionPlan": SubscriptionStrategy},
)
In the Unleash UI, create a strategy named “SubscriptionPlan” with a parameter “plans” (e.g., “premium,enterprise”). The SDK evaluates this against the context you provide at check time.
Strategy composition
Unleash evaluates multiple strategies with OR logic by default — if any strategy matches, the toggle is enabled. For AND logic, use constraints or segment conditions:
{
"name": "flexibleRollout",
"parameters": {"rollout": "50", "groupId": "new-checkout"},
"constraints": [
{"contextName": "country", "operator": "IN", "values": ["US", "CA"]},
{"contextName": "plan", "operator": "NOT_IN", "values": ["free"]}
]
}
Constraints narrow down who within a strategy’s target actually gets the feature.
Toggle lifecycle management
Creation to cleanup pipeline
- Proposal: Developer creates a toggle in Unleash with type (release/experiment/operational) and expected lifetime.
- Development: Code checks the toggle. Both paths (enabled/disabled) have tests.
- Rollout: Gradual percentage increase, monitoring error rates and performance.
- Full rollout: Toggle enabled for 100%.
- Cleanup: Remove the toggle check from code. Delete the toggle from Unleash.
Detecting stale toggles
Unleash marks toggles as “potentially stale” based on their type and age:
- Release toggles: stale after 40 days
- Experiment toggles: stale after 40 days
- Operational toggles: never automatically stale
Automate cleanup reminders:
import httpx
from datetime import datetime, timedelta
def find_stale_toggles():
response = httpx.get(
"http://unleash:4242/api/admin/features",
headers={"Authorization": f"*:{admin_token}"}
)
stale = []
for toggle in response.json()["features"]:
if toggle["stale"] or toggle.get("potentially_stale"):
stale.append({
"name": toggle["name"],
"type": toggle["type"],
"created": toggle["createdAt"],
})
return stale
Run this weekly and post results to Slack.
Testing patterns
Unit testing with overrides
Never let tests depend on the Unleash server. Mock toggle state:
from unittest.mock import patch
def test_new_checkout_enabled():
with patch.object(client, "is_enabled", return_value=True):
result = process_checkout(order)
assert result.used_new_flow
def test_new_checkout_disabled():
with patch.object(client, "is_enabled", return_value=False):
result = process_checkout(order)
assert result.used_old_flow
Integration testing toggle combinations
For critical features, test both paths:
@pytest.mark.parametrize("toggle_state", [True, False])
def test_checkout_both_paths(toggle_state):
with patch.object(client, "is_enabled", return_value=toggle_state):
result = process_checkout(order)
assert result.success
Avoiding combinatorial explosion
With 20 toggles, testing all combinations means 2^20 = 1 million test cases. Instead:
- Test each toggle independently (both states).
- Identify toggle interactions (rare — most toggles are independent).
- Test known interaction pairs explicitly.
- Use production monitoring to catch unexpected interactions.
Multi-environment rollout
Unleash supports environment-specific toggle states. A toggle can be enabled in staging but disabled in production:
Toggle: new-payment-flow
├── development: enabled (100%)
├── staging: enabled (100%)
└── production: enabled (10%)
The Python SDK connects with an environment-scoped API token. Each environment sees only its own toggle state:
# Production SDK
prod_client = UnleashClient(
url="http://unleash:4242/api",
app_name="order-service",
custom_headers={"Authorization": f"production:{prod_token}"},
)
# Staging SDK
staging_client = UnleashClient(
url="http://unleash:4242/api",
app_name="order-service",
custom_headers={"Authorization": f"staging:{staging_token}"},
)
Metrics and observability
The SDK reports metrics to Unleash: how many times each toggle was checked, and how many times it evaluated to true vs false. Use this data to:
- Identify unused toggles (zero checks = safe to delete)
- Monitor rollout progress (increasing true count as percentage rises)
- Detect unexpected patterns (a toggle suddenly evaluating differently)
Export Unleash metrics to Prometheus for dashboard integration:
from prometheus_client import Counter
TOGGLE_CHECKS = Counter(
"feature_toggle_checks_total",
"Feature toggle evaluations",
["toggle_name", "enabled"]
)
def checked_is_enabled(toggle_name, context=None):
result = client.is_enabled(toggle_name, context)
TOGGLE_CHECKS.labels(toggle_name=toggle_name, enabled=str(result)).inc()
return result
Operational patterns
Circuit breaker toggles
Use operational toggles as circuit breakers for external dependencies:
def call_payment_provider(order):
if not client.is_enabled("payments-circuit-open"):
try:
return payment_provider.charge(order)
except TimeoutError:
# Could auto-enable the circuit toggle via Unleash API
raise
else:
return enqueue_for_retry(order)
Flip the toggle in the dashboard to instantly bypass a failing payment provider during an incident. No deployment needed.
Maintenance mode
@app.before_request
def check_maintenance():
if client.is_enabled("maintenance-mode"):
return render_template("maintenance.html"), 503
Enable from the dashboard for planned maintenance windows.
Self-hosting Unleash
Unleash is open source. Run it alongside your services:
# docker-compose.yml
services:
unleash:
image: unleashorg/unleash-server:latest
environment:
DATABASE_URL: postgres://unleash:password@db/unleash
ports:
- "4242:4242"
db:
image: postgres:15
environment:
POSTGRES_DB: unleash
POSTGRES_USER: unleash
POSTGRES_PASSWORD: password
Self-hosted Unleash requires minimal resources: 256 MB RAM, a PostgreSQL instance, and that is it. Backup the database; all toggle state lives there.
One thing to remember: Production feature toggles require disciplined lifecycle management (create, rollout, cleanup), failure-safe defaults, environment-scoped configuration, and observability — the toggle check itself is trivial, but the operational practices around it determine success or technical debt.
See Also
- Python Adaptive Learning Systems How Python builds learning apps that adjust to each student like a personal tutor who knows exactly what you need next.
- Python Airflow Learn Airflow as a timetable manager that makes sure data tasks run in the right order every day.
- Python Altair Learn Altair through the idea of drawing charts by describing rules, not by hand-placing every visual element.
- Python Automated Grading How Python grades homework and exams automatically, from simple answer keys to understanding written essays.
- Python Batch Vs Stream Processing Batch processing is like doing laundry once a week; stream processing is like a self-cleaning shirt that cleans itself constantly.