Python
The edgeflags package provides async and sync clients for reading feature flags and configs from Python 3.10+. Both interfaces share the same API surface — choose async for asyncio applications or sync for scripts and traditional web frameworks.
SDKs are also available for JavaScript / TypeScript and React.
Installation
pip install edgeflagsQuick start
from edgeflags import AsyncEdgeFlags
ef = AsyncEdgeFlags( token="ff_production_abc123", base_url="https://edgeflags.net", context={ "user_id": "u_42", "plan": "premium", "custom": {"country": "US"}, },)
await ef.init()
dark_mode = ef.flag("dark_mode", False)limits = ef.config("api_limits", {"requests_per_minute": 100})from edgeflags import EdgeFlags
ef = EdgeFlags( token="ff_production_abc123", base_url="https://edgeflags.net", context={ "user_id": "u_42", "plan": "premium", "custom": {"country": "US"}, },)
ef.init()
dark_mode = ef.flag("dark_mode", False)limits = ef.config("api_limits", {"requests_per_minute": 100})After init() completes, all reads are synchronous from the local cache. The client polls for updates in the background.
Configuration
Pass these options to the constructor (both AsyncEdgeFlags and EdgeFlags accept the same parameters):
| Option | Type | Default | Description |
|---|---|---|---|
token | str | required | Bearer token for authentication |
base_url | str | required | EdgeFlags API URL |
context | EvaluationContext | None | User context for targeting evaluation |
polling_interval | float | 60.0 | Background poll interval in seconds |
bootstrap | dict | None | Initial flag/config values for instant reads before init() |
debug | bool | False | Log SDK activity to stderr |
Evaluation context
The context dict is sent with every evaluation request and drives targeting rules:
from edgeflags import EvaluationContext
context: EvaluationContext = { "user_id": "u_42", "email": "alice@example.com", "phone": "+1234567890", "plan": "premium", "segments": ["beta-testers"], "environment": "production", "custom": {"country": "US", "beta_enrolled": True},}EvaluationContext is a TypedDict — all fields except custom are optional.
Reading flags
Use flag() to read a flag value. Pass a default as the second argument:
# Returns the flag value, or None if not foundraw = ef.flag("dark_mode")
# Returns bool (default provides fallback)dark_mode = ef.flag("dark_mode", False)
# String flagsbanner = ef.flag("banner_text", "")
# Numeric flagslimit = ef.flag("request_limit", 1000)
# Dict flagstheme = ef.flag("theme_override", {"primary": "#000"})Use all_flags() to get every cached flag as a dict[str, Any].
Reading configs
Use config() the same way:
providers = ef.config("payment_providers", { "stripe_enabled": True, "paypal_enabled": False,})
all_configs = ef.all_configs()Identifying users
Call identify() to update the evaluation context and immediately refresh all values:
await ef.identify({ "user_id": "u_99", "email": "jane@example.com", "plan": "enterprise", "custom": {"beta_enrolled": True},})
# Flags and configs now reflect the new useref.identify({ "user_id": "u_99", "email": "jane@example.com", "plan": "enterprise", "custom": {"beta_enrolled": True},})
# Flags and configs now reflect the new userEvents
The client emits three events:
| Event | Payload | When |
|---|---|---|
ready | None | init() completes successfully |
change | ChangeEvent | Flag or config values change after a poll |
error | Exception | Network or API error during polling |
# Subscribe — returns an unsubscribe functiondef on_change(event): for change in event.flags: print(f"Flag {change.key}: {change.previous} -> {change.current}")
off = ef.on("change", on_change)
ef.on("error", lambda err: print(f"EdgeFlags polling error: {err}"))
# Unsubscribeoff()Bootstrap and offline fallback
Provide bootstrap values so flags are available immediately, before the first network request:
ef = EdgeFlags( token="ff_production_abc123", base_url="https://edgeflags.net", bootstrap={ "flags": {"dark_mode": False, "new_checkout": True}, "configs": {"api_limits": {"requests_per_minute": 500}}, },)
# Available immediately — no init() neededdark_mode = ef.flag("dark_mode", False)
# init() will replace bootstrap values with live dataef.init()Testing
Use the mock client helpers to create a client that returns static values without making network requests:
from edgeflags import create_mock_client
mock = create_mock_client( flags={"dark_mode": True, "new_checkout": False}, configs={"api_limits": {"requests_per_minute": 9999}},)
# Works like a real clientmock.flag("dark_mode", False) # Truemock.config("api_limits") # {"requests_per_minute": 9999}from edgeflags import create_mock_client_sync
mock = create_mock_client_sync( flags={"dark_mode": True, "new_checkout": False}, configs={"api_limits": {"requests_per_minute": 9999}},)
# Works like a real clientmock.flag("dark_mode", False) # Truemock.config("api_limits") # {"requests_per_minute": 9999}Cleanup
Stop the background polling and release resources when you’re done:
await ef.aclose()AsyncEdgeFlags also works as an async context manager:
async with AsyncEdgeFlags(token=token, base_url=url) as ef: await ef.init() dark_mode = ef.flag("dark_mode", False)# aclose() called automaticallyef.destroy()EdgeFlags also works as a context manager:
with EdgeFlags(token=token, base_url=url) as ef: ef.init() dark_mode = ef.flag("dark_mode", False)# destroy() called automatically