Python SDK
Python feature flags, one call in your code
One client, one is_enabled call, and the decision comes from a ruleset streamed into the process. Gate a new ML model, a rewritten task, or a whole view, then roll it out gradually and kill it instantly if the error rate moves.
01 · Drop it in
The planned Python API, exactly as it will ship
The SDK is part of the early-access program, and this is its real surface: one install, one client, one call per decision. Every toggle, rollout step and kill-switch flip you make in the console reaches this code in about 3 seconds, with no redeploy.
Sync and async APIs ship together; type hints included.
import os
from toggled import Toggled
flags = Toggled(sdk_key=os.environ["TOGGLED_SDK_KEY"])
def checkout(request):
if flags.is_enabled(
"checkout.new-flow",
user_id=request.user.id,
plan=request.user.plan,
):
return render_new_checkout(request)
return render_classic_checkout(request)
02 · Built for Python
What Python feature flags get you
Django, Flask, FastAPI, Celery
The client is framework-agnostic: use it in a view, a middleware, an async endpoint or a background task with the same call.
Sub-millisecond checks
Evaluation is local, so flag checks belong in your request path and even in loops. No network round trip per decision.
Kill switches during incidents
Flip a flag off in the console and every worker process converges in about 3 seconds, without restarting gunicorn or redeploying.
03 · Then the usual lifecycle
Flag it, target it, roll it out, measure, kill
Once checkout.new-flow exists in your Python code, everything else happens in the console: target the internal team, run a gradual rollout from 1% to 100%, read the impact with built-in A/B testing, and keep the kill switch under your thumb. The full walkthrough is on how Toggled works.
Flat pricing applies here too: unlimited seats and unlimited flags on every plan, so the whole Python team gets an account. Numbers are on the pricing page.
Same flags, other stacks
Cohorts agree across SDKs: a user in the 10% on your Python side is in the same 10% everywhere else.
Java, Rails, iOS and Android SDKs share the same core and ship with early access.
04 · Where the check goes
Where a flag check belongs in a Python codebase
01
One client, created once, after the fork
The client belongs in your app factory or a post-fork hook, held as a single module-level object and imported where it is needed. Building one per request or per Celery task re-fetches the ruleset and turns a local dict lookup back into an HTTP call.
02
In the view, or in middleware for whole-route gates
A per-user variant belongs in the view that renders it. A beta area that should simply not exist for everyone else belongs in Django middleware or a FastAPI dependency, where one check covers every route underneath it.
03
Behind a dependency, never a hard import
Expose the client through Depends(get_flags) in FastAPI, or a small accessor function in Django, so a test can swap in a fake with one line. A module that imports the live client at the top of the file is a module you cannot test without monkeypatching internals.
05 · Gotchas
Three ways Python feature flags go wrong
Gotcha 01
Preload plus fork kills the refresh thread
With gunicorn preload_app or uWSGI preloading, a client built at import time is forked into every worker without its background thread, because threads are not inherited across fork. The workers then serve a ruleset frozen at boot and never see a kill switch flip. Build the client in gunicorn post_fork or the uWSGI postfork hook.
Gotcha 02
The flag check inside the ORM loop
Local evaluation is cheap, so calling is_enabled inside a loop over ten thousand rows is genuinely fine. What is not fine is passing a per-row user object whose attribute access triggers a lazy query each iteration. The flag is not the cost there; the context you built for it is. Resolve the user context once, outside the loop.
Gotcha 03
Async views calling a blocking client
In FastAPI or a Django async view, a blocking call inside a coroutine stalls the event loop for as long as it blocks. A local evaluation is fast enough that this is normally invisible, but use the async API when the SDK offers one, and make sure a stale cache never falls back to a synchronous HTTP fetch inside async code.
06 · Testing
Testing both paths in Python
Make the client a pytest fixture rather than a module-level import, so each test can be handed a fake with a dict of values. Parametrize turns the on and off pair into a single test function that covers both branches. For Celery, run the task eagerly with the fake bound, which catches the case where the worker reads a different value than the caller that enqueued the job.
# conftest.py
import pytest
from myapp import checkout
class FakeFlags:
def __init__(self, values):
self.values = values
def is_enabled(self, key, **context):
return self.values.get(key, False)
@pytest.fixture
def fake_flags(monkeypatch):
def bind(values):
monkeypatch.setattr(checkout, "flags", FakeFlags(values))
return bind
# test_checkout.py
@pytest.mark.parametrize("enabled,expected", [(True, "new"), (False, "classic")])
def test_checkout_takes_both_paths(client, fake_flags, enabled, expected):
fake_flags({"checkout.new-flow": enabled})
assert client.post("/checkout").json()["flow"] == expected
07 · People also ask
Python feature flag questions, answered
How do you add feature flags to Django or Flask?
Create one client at application startup and call is_enabled inside a view, a middleware or a context processor, passing the request user. The client holds the ruleset in memory, so the check never touches the network. In Django, a small template tag keeps the branch readable without putting flag logic into templates.
Do feature flags work in Celery tasks?
Yes, but the task has to evaluate the flag itself or receive the decision in its payload. A worker is a separate process with its own client, so a flag flipped between enqueue and execution changes the outcome mid-flight. If a job must behave exactly as it did when queued, put the decision in the message.
Does the GIL make feature flag checks slow in Python?
No. A local check is a dict lookup and a hash, holding the GIL for microseconds, so it does not meaningfully contend with your other threads. The thing that would hurt is an SDK making an HTTP call per check: that releases the GIL, but it adds real network latency to every request you serve.
How do feature flags survive a gunicorn or uWSGI fork?
Create the client after the fork, inside a post-fork hook, so each worker builds its own connection and its own background refresh thread. Threads do not survive fork, so a client created in the master during preload arrives in the children with a dead updater and a ruleset frozen at boot time.
More answers, about the product rather than the code, live on the FAQ.
Early access · launching soon
Put your first Python feature behind a flag
Join the early-access list and be first in line when spots open. Flat pricing, unlimited seats, no card required.
No card required · your data stays yours
Keep exploring
More feature flag resources
- Go Feature Flags With Sub-Millisecond Checks
- Feature Flags Platform
- Managing Feature Flags
- Feature Flag Tools
- A/B Testing Software Powered by Feature Flags
- Feature Flag Service Use Cases for Product Teams
- Feature Flag Software Pricing, Unlimited Seats
- Feature Flags Explained
- LaunchDarkly Alternative With Flat Pricing
- LaunchDarkly Pricing
- Best Feature Flag Tools in 2026, Compared
- GitLab Feature Flags