appsettings.json wire format and toggle-value grammar, so they interoperate.
Looking for the C# library? See the .NET docs.
Zero call-site noise
Decorate a function with @toggle; it runs only when its toggle is on. No if, no wrapper, no injected service.
Standard-library core
No third-party dependencies. The Azure App Config provider is the only optional extra.
appsettings.json
Same section names and value grammar as .NET, so config is cross-language and wire-compatible.
Strategy pipeline
Percentage rollouts, A/B buckets, blue-green slots, user and attribute targeting, per-user overrides.
Fail-safe providers
HTTP, env vars, and Azure App Config feed a buffer that flushes to disk โ reads survive a provider going offline.
ftrio lint
A CI gate that fails when a @toggle function has no matching key in appsettings.json.
๐ Quickstart
1. Decorate a function
The toggle key defaults to the function's own name.
from ftrio import toggle
@toggle
def send_welcome_email(user):
... # runs only when the "send_welcome_email" toggle is on
2. Add a matching key to appsettings.json
{
"Toggles": {
"send_welcome_email": true
}
}
3. Call it normally
When the toggle is off, the call returns None without running the body.
send_welcome_email(user) # no-op (returns None) when the toggle is off
appsettings.json on disk at all,
every toggle reads on โ a fresh app stays fully functional before any config
exists. A present file missing a key raises ToggleDoesNotExistError; a present key
with an uninterpretable value raises ToggleParsedOutOfRangeError.
Async
Use @toggle_async for coroutines. When the toggle is off it returns an awaitable that resolves to None, so await is always safe.
from ftrio import toggle_async
@toggle_async
async def sync_orders():
...
await sync_orders() # resolves to None when off โ always awaitable
๐งฑ The builder pipeline
Plain true/false is the baseline. For richer decisions, build a parser
fluently and install it as the ambient parser the decorators use:
from ftrio import ToggleParserProvider
ToggleParserProvider.configure_builder(lambda builder: builder
.with_context_strategies(context_accessor) # user targeting + attributes + A/B
.with_percentage_rollout() # "20%"
.with_blue_green() # "blue" / "green" from appsettings.json
.with_overrides()) # per-user TogglesOverrides, checked first
Strategies are tried in registration order; the first whose can_handle accepts the
raw value owns the decision. BooleanStrategy is always appended last, so plain
booleans keep working under any chain.
Toggle-value grammar
| Value | Strategy | Meaning |
|---|---|---|
true / false / 1 / 0 | Boolean | plain on/off |
20% | PercentageRollout | on for ~20% of calls (random per call) |
blue / green | BlueGreen | on when it names the active deployment slot |
users:alice,bob | UserTargeting | on for the listed user ids |
attribute:plan equals premium | AttributeRule | on when the rule matches the user's attribute |
ab:50 / ab:50:salt | ABTest | deterministic per-user 50% bucket |
A/B bucketing is stable and identical to the .NET implementation (SHA-256, first four bytes as a little-endian signed int, absolute value modulo 100) โ the same user buckets the same way in both languages.
Per-user overrides
TogglesOverrides win unconditionally, before any strategy runs โ handy for QA,
support escalations, or dogfooding:
{
"Toggles": { "NewCheckout": "ab:50" },
"TogglesOverrides": { "NewCheckout": { "alice": true } }
}
๐ Providers and the buffer model
External sources feed a ToggleProviderBuffer, which flushes staged values to
appsettings.json atomically on an interval. The file stays the on-disk source of
truth, so reads survive a provider going offline (fail-safe).
from ftrio import ToggleProviderBuffer
from ftrio.providers import HttpToggleParser
buffer = ToggleProviderBuffer()
HttpToggleParser("https://flags.example.com/toggles", buffer) # polls, stages, flushes
Available providers: HttpToggleParser (standard library),
EnvironmentVariableToggleParser (standalone or buffer mode), and
AzureAppConfigToggleParser (needs the ftrio[azure] extra). Each exposes
close() and context-manager support.
CompositeToggleParser chains parsers with first-wins fallthrough โ e.g. env-var
overrides, then a remote provider, then appsettings.json as the durable fallback.
๐ The ftrio lint CLI
The .NET library ships a Roslyn analyzer (diagnostic FTRIO001) that fails the build
when a [Toggle] method has no matching key in appsettings.json. The Python
equivalent is a CLI you run in CI:
$ ftrio lint path/to/project
path/to/project/mod.py:8: FTRIO001: Function 'MissingOne' is decorated with @toggle but has no entry in the Toggles section of appsettings.json
1 toggle(s) missing from appsettings.json.
It walks the tree with ast, resolves each @toggle / @toggle_async
key, and exits non-zero on findings so it can gate a pipeline. Non-project directories
(.venv, .git, build, dist, __pycache__,
and the usual tool caches) are skipped by default.
$ ftrio lint . --exclude tests --exclude "*_generated.py"
$ ftrio lint . --exclude tests,scripts
Use --no-default-excludes to scan everything, and -v/--verbose to see
what is being scanned.
๐ Configuration
appsettings.json keeps the .NET section names (Toggles,
TogglesOverrides, FtrIO) for cross-language and wire compatibility โ the
HTTP provider returns this exact shape.
{
"FtrIO": {
"ReloadOnChange": true, // re-read on each lookup; live edits apply
"FlushInterval": 5,
"Environment": "Production", // overlays appsettings.Production.json
"BlueGreen": { "CurrentSlot": "blue", "KnownSlots": "blue,green" }
}
}
The active environment resolves from FtrIO:Environment, then
ASPNETCORE_ENVIRONMENT, then DOTNET_ENVIRONMENT (with
FTRIO_ENVIRONMENT as an additive Python-native alias).
Supported Python versions
See the PyPI page for the authoritative supported range.
๐ฎ Playground
Run the bundled playground to watch toggles flip live:
$ python -m playground
It cycles four users every two seconds and prints each toggle's ON/OFF state, honouring live
edits to playground/appsettings.json.