Feature toggles for Python, decorate a function with @toggle and it becomes gated by its own name
Project description
FtrIO (Python)
Feature toggles for Python, ported faithfully from the .NET
FtrIO library. Decorate a method, add a key
to appsettings.json, and the method runs only when the toggle is on. Richer
decisions, percentage rollouts, A/B buckets, deployment slots, user and attribute
targeting, per-user overrides, are layered in through a strategy chain.
The core depends on the standard library only.
Installation
pip install ftrio
The core has no third-party dependencies. The Azure App Configuration provider needs an optional extra:
pip install "ftrio[azure]"
(Contributors working on FtrIO itself want the editable dev install instead; see Development.)
Quickstart
-
Decorate a method. The toggle key defaults to the method's own name.
from ftrio import toggle @toggle def send_welcome_email(user): ... # runs only when the "send_welcome_email" toggle is on
-
Add a matching key to
appsettings.jsonin your working directory:{ "Toggles": { "send_welcome_email": true } }
-
Call it normally. When the toggle is off, the call returns
Nonewithout running the body (an async@toggle_asyncreturns an awaitable resolving toNone, soawaitis always safe).
If there is no appsettings.json on disk at all, every toggle reads on, a fresh
app stays fully functional before any config exists. A present file with a
missing key raises ToggleDoesNotExistError; a present key with an
uninterpretable value raises ToggleParsedOutOfRangeError.
The builder pipeline
Plain true/false is the baseline. For richer decisions, build a parser and
install it as the ambient parser used by the decorators:
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 grammars:
| 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 or ab:50:salt |
ABTest | deterministic per-user 50% bucket |
A/B bucketing is stable: the same user, key (and salt) always bucket identically, and identically to the .NET implementation (SHA-256, first four bytes as a little-endian signed int, absolute value modulo 100).
Per-user overrides (TogglesOverrides) win unconditionally, before any strategy:
{
"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. appsettings.json 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]-decorated method has no matching key in appsettings.json.
The Python equivalent is a CLI you can 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, so it never descends into installed
dependencies. Skip additional paths with --exclude (repeatable, and also accepts
a comma-separated list); patterns are globs matched against each path component and
the relative path:
$ 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. Test fixtures that create their config dynamically are a common case
for --exclude tests.
Configuration
appsettings.json keeps the .NET section names (Toggles, TogglesOverrides,
FtrIO) for cross-language and wire compatibility, the HTTP provider returns this
exact shape. Notable FtrIO settings: ReloadOnChange (re-read on each lookup so
live edits apply without a restart), FlushInterval, Environment (overlays
appsettings.{Environment}.json), and BlueGreen:CurrentSlot / KnownSlots.
The active environment resolves from FtrIO:Environment, then
ASPNETCORE_ENVIRONMENT, then DOTNET_ENVIRONMENT (with FTRIO_ENVIRONMENT as an
additive Python-native alias).
Playground
$ python -m playground
Cycles four users every two seconds and prints each toggle's ON/OFF state,
honouring live edits to playground/appsettings.json.
Development
$ pip install -e ".[dev]"
$ pytest --cov=ftrio
$ ruff check ftrio
$ mypy ftrio
See PORTING_NOTES.md for every deviation from a literal 1:1 port of the .NET source.
Releasing and changelog
Releases are published to PyPI from a GitHub Release via Trusted Publishing; the step-by-step checklist is in RELEASING.md. Notable changes are recorded in CHANGELOG.md.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file ftrio-1.0.0.tar.gz.
File metadata
- Download URL: ftrio-1.0.0.tar.gz
- Upload date:
- Size: 35.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
fe2668534ec3a9fc0f7c67e663b115a3be65481601181e247b0e4854139b3676
|
|
| MD5 |
3ef0d6cf2fa733ae73a213a3ee2f3632
|
|
| BLAKE2b-256 |
73ecf4376662701777bb45f2fc612693fee0ea287a976b468d7a04e894c783ea
|
Provenance
The following attestation bundles were made for ftrio-1.0.0.tar.gz:
Publisher:
publish.yml on FtrOnOff/ftrio-python
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ftrio-1.0.0.tar.gz -
Subject digest:
fe2668534ec3a9fc0f7c67e663b115a3be65481601181e247b0e4854139b3676 - Sigstore transparency entry: 2047486561
- Sigstore integration time:
-
Permalink:
FtrOnOff/ftrio-python@1c04c1c07200f0fdd13fce68e4b1c819b075420c -
Branch / Tag:
refs/tags/v1.0.0 - Owner: https://github.com/FtrOnOff
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@1c04c1c07200f0fdd13fce68e4b1c819b075420c -
Trigger Event:
release
-
Statement type:
File details
Details for the file ftrio-1.0.0-py3-none-any.whl.
File metadata
- Download URL: ftrio-1.0.0-py3-none-any.whl
- Upload date:
- Size: 44.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b332a03851ec2c11a653b8dc9771c64b1438d961d11e65d3764f86b88b40ee90
|
|
| MD5 |
ffa3cf9bfd47fa322547362713da2104
|
|
| BLAKE2b-256 |
9e2557001e4579469d680bdcb2eb915691db06a66f04e8ead5c869401ccaab23
|
Provenance
The following attestation bundles were made for ftrio-1.0.0-py3-none-any.whl:
Publisher:
publish.yml on FtrOnOff/ftrio-python
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ftrio-1.0.0-py3-none-any.whl -
Subject digest:
b332a03851ec2c11a653b8dc9771c64b1438d961d11e65d3764f86b88b40ee90 - Sigstore transparency entry: 2047486575
- Sigstore integration time:
-
Permalink:
FtrOnOff/ftrio-python@1c04c1c07200f0fdd13fce68e4b1c819b075420c -
Branch / Tag:
refs/tags/v1.0.0 - Owner: https://github.com/FtrOnOff
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@1c04c1c07200f0fdd13fce68e4b1c819b075420c -
Trigger Event:
release
-
Statement type: