Flask Integration
toggly-flask provides the Toggly extension, request helpers, view decorators and a Jinja2 context object.
Installation
pip install toggly-flask
Quick Start
Save this as app.py, set TOGGLY_APP_KEY in the process environment, and run flask --app app run.
import os
from flask import Flask
from toggly_flask import Toggly
app = Flask(__name__)
app.config.update(
TOGGLY_APP_KEY=os.environ.get("TOGGLY_APP_KEY"),
TOGGLY_ENVIRONMENT="Production",
TOGGLY_FEATURE_DEFAULTS={"new-dashboard": False},
)
toggly = Toggly(app)
@app.route("/dashboard")
def dashboard():
enabled = toggly.is_enabled("new-dashboard", default=False)
return {"version": "v2" if enabled else "v1"}
Without a key, the local defaults let the app start without fetching definitions. Create new-dashboard in the configured application/environment, enable it, and revisit /dashboard after the next refresh (180 seconds by default). Keep authentication and permission checks alongside feature checks.
Configuration and factory pattern
The extension reads TOGGLY_APP_KEY, TOGGLY_ENVIRONMENT, TOGGLY_FEATURE_DEFAULTS, TOGGLY_BASE_URL, TOGGLY_REFRESH_INTERVAL, TOGGLY_USE_SIGNED_DEFINITIONS, TOGGLY_CONNECT_TIMEOUT, TOGGLY_REQUEST_TIMEOUT, TOGGLY_ENABLE_USAGE_TRACKING, TOGGLY_DISABLE_BACKGROUND_REFRESH and TOGGLY_DEBUG before initialization.
import os
from flask import Flask
from toggly_flask import Toggly
toggly = Toggly()
def create_app():
app = Flask(__name__)
app.config["TOGGLY_APP_KEY"] = os.environ.get("TOGGLY_APP_KEY")
toggly.init_app(app)
return app
Preconfigured client
Initialize and register a supplied core client once at startup so the extension and native decorators use the same client. This offline configuration is useful for application tests:
from flask import Flask
from toggly import TogglyClient, TogglyConfig, set_default_client
from toggly_flask import Toggly
client = TogglyClient(TogglyConfig(
feature_defaults={"new-dashboard": True},
disable_background_refresh=True,
enable_live_updates=False,
enable_usage_tracking=False,
))
client.init()
set_default_client(client)
app = Flask(__name__)
toggly = Toggly(app, client=client)
Call client.close() once at worker shutdown or fixture teardown. Flask's per-request teardown is not a suitable place to close this shared client. With Gunicorn, initialize the client in each worker without application preload and use an application-owned worker shutdown hook to close it.
Request evaluation
get_toggly() returns the extension; g.toggly is the request helper. Both evaluate with the current request. The extension accepts an explicit context=; the request helper accepts a flag key and default.
from flask import g
from toggly_flask import get_toggly
@app.route("/settings")
def settings():
ext = get_toggly()
return {
"dashboard": g.toggly.is_enabled("new-dashboard"),
"premium": ext.evaluate_gate(["premium", "trial"], requirement="any"),
}
View decorators
Place the route decorator outermost. feature_flag_required aborts with HTTP 403 by default; status_code, redirect_url, or fallback_view customize the disabled response. feature_gate_required takes FeatureRequirement.ALL (default) or FeatureRequirement.ANY.
from toggly import FeatureRequirement
from toggly_flask import feature_flag_required, feature_gate_required
@app.route("/beta")
@feature_flag_required("beta-access", status_code=404)
def beta_view():
return {"message": "Beta content"}
@app.route("/premium")
@feature_gate_required(["premium", "vip"], requirement=FeatureRequirement.ANY)
def premium_content():
return {"access": "premium"}
Switch between views
from toggly_flask import feature_flag_switch
def old_checkout():
return {"version": "v1"}
def new_checkout():
return {"version": "v2"}
app.add_url_rule("/checkout-version", "checkout_version", feature_flag_switch(
"new-checkout", enabled_view=new_checkout, disabled_view=old_checkout
))
The decorators also work on ordinary Flask Blueprint routes. Use Flask's normal error handlers to customize the response returned by abort.
FeatureFlagBlueprint
FeatureFlagBlueprint applies a native feature gate to routes registered through its route method. Register the underlying Flask Blueprint with the application:
from flask import Blueprint
from toggly_flask import FeatureFlagBlueprint
beta = Blueprint("beta", __name__, url_prefix="/beta")
gated_beta = FeatureFlagBlueprint(beta, "beta-access")
@gated_beta.route("/welcome")
def beta_welcome():
return {"message": "Beta content"}
app.register_blueprint(beta)
With beta-access enabled, /beta/welcome returns the view response; otherwise the native gate returns HTTP 403. Routes added directly to beta do not automatically receive this wrapper's gate. Keep normal authentication and authorization alongside the feature check.
Jinja2 templates
The extension registers a toggly context object. Use toggly.check for an exact flag key, or attribute access where underscores map to hyphens. Render your template using Flask's render_template or render_template_string within a request.
{% if toggly.check("new-navigation") %}
<nav>New navigation</nav>
{% else %}
<nav>Legacy navigation</nav>
{% endif %}
{% if toggly.is_disabled.maintenance_mode %}
<p>System operational</p>
{% endif %}
For multiple-feature gates, evaluate g.toggly.evaluate_gate(...) in the view and pass the boolean to render_template. toggly.flags is the client's flag snapshot; use check for decisions that require the current user or Order.
User context
Flask-Login is optional. If it is installed, initialize LoginManager and register your application's user loader before serving requests, including anonymous requests. Installing the package alone does not configure the request's current_user. Keep your existing authentication flow; the runnable Flask sample shows a complete local demonstration setup.
A factory can accept the application's existing user lookup function:
from flask_login import LoginManager
login_manager = LoginManager()
def configure_login(app, load_user_by_id):
login_manager.init_app(app)
login_manager.user_loader(load_user_by_id)
Call configure_login(app, load_user_by_id) during application setup with your existing lookup function. The function must accept the stored user ID and return the corresponding Flask-Login user object, or None if that user no longer exists. Set the application's session SECRET_KEY through its normal configuration before using session login. This configures loading only; it does not implement a login endpoint.
With Flask-Login configured, the adapter reads the authenticated current_user.id (or get_id()), roles or groups, and email/admin traits. Request path, method and remote address are traits. Complete authentication before any feature evaluation. For custom targeting, pass a new EvaluationContext or a copy from get_context_from_request() to the extension's is_enabled(..., context=...).
Claims and request filters
from flask import request
from toggly import HttpRequestMapper
from toggly_flask import get_context_from_request
@app.route("/browser-offer")
def browser_offer():
context = HttpRequestMapper.merge_into(
request.headers, get_context_from_request()
)
return {"enabled": toggly.is_enabled("browser-offer", context=context)}
The mapper fills structured browser/language/country fields. Only trust country headers from your proxy. Add verified claims with context.with_claims(...); the adapter does not automatically map claims or request segment fields from traits. Keep user targeting per request and do not change the shared client's identity. Remote variants have a separate client-wide startup context (TOGGLY_ENABLE_VARIANTS, TOGGLY_IDENTITY, TOGGLY_VARIANT_GROUPS, TOGGLY_VARIANT_CLAIMS).
For a per-user remote assignment action, create an isolated core client after identity, groups and trusted claims are known. Supply those values in its initial configuration, initialize it, read get_variant, then close it without registering it as the shared default. This action adds remote fetching; ordinary local feature evaluations continue to use the worker-owned client. An absent assignment is a valid result.
The native feature_variant decorator reads feature-state metadata for named dispatch. Current core feature state does not supply an assigned variant in that metadata; its enabled view and disabled default view are ordinary feature-gated behavior. Use the core get_variant API for a remote assignment.
Entity context
Set g.toggly_entity before the first request-helper check or template render. For entity-gated decorators, set it in a before_request handler before the view runs. Use an authorized domain object in your app; this example uses two fixed demonstration Orders.
from flask import g
from toggly import TogglyEntityContext
@app.route("/checkout")
def checkout():
g.toggly_entity = TogglyEntityContext(
kind="Order", key="order-123", attributes={"Vip": True, "Total": 120.0}
)
enabled = g.toggly.is_enabled("ExpressCheckout")
other = TogglyEntityContext(
kind="Order", key="order-456", attributes={"Vip": False, "Total": 20.0}
)
other_enabled = toggly.is_enabled(
"ExpressCheckout", context=g.toggly.context.with_entity(other)
)
return {"express": enabled, "other_express": other_enabled}
Configure ExpressCheckout with an Order Context Property condition Vip == true. The two calls preserve the same user but evaluate different Orders. The helper caches its context, so use with_entity for the second Order rather than replacing g.toggly_entity after a check. See Python entity mapping for local mappers and optional schema registration at startup.
Testing
Use the preconfigured offline client above in a fixture, register the real routes, and close it after the test. Flask's test client exercises the extension and decorators:
# app contains the dashboard route from Quick Start.
with app.test_client() as http:
response = http.get("/dashboard")
assert response.status_code == 200
assert response.json == {"version": "v2"}
Test disabled defaults as well as enabled defaults. For targeting, load real definitions through a snapshot provider, authenticate alternating users, and assert opposite Order outcomes. The Flask helpers and decorators use the synchronous client, including when invoked from an async Flask view.
Learning path
Run the Flask sample for a complete application with native routes, templates, request context and client lifecycle wiring.
Start with /dashboard, add the /beta gate, then render a Jinja template and try the two-Order route. Browse the Samples catalog for runnable projects and setup instructions. Read the adapter's extension and decorators to follow configuration, request extraction and evaluation.