Skip to main content

toggleflag

Python 3.10+ MIT License Tests

Lightweight feature flags for Python — no server, no dependencies.

import toggle

flags = toggle.Flags()

flags.define("new_checkout", default=True)
flags.define("experiment_v2", variants=["control", "treatment"], rollout_pct=30)

flags.is_on("new_checkout")                              # True
flags.variant("experiment_v2", user_id="u1")            # "treatment" (30% of the time)

with flags.context(user_id="u1", country="US"):
    flags.is_on("us_only_feature")                       # checks per-user + per-country

Why toggleflag?

Feature flags are essential for modern software delivery. They enable teams to ship code behind toggles, decouple deployment from release, run A/B experiments, and instantly disable broken features in production.

Existing solutions have significant trade-offs:

Concern toggleflag LaunchDarkly Unleash django-flags
Requires a server ❌ No ✅ Yes ✅ Yes ❌ No
External dependencies Zero SDK dependency SDK dependency Django framework
Setup time < 1 minute Account + SDK setup Self-hosted cluster Django project
Per-user targeting ✅ ✅ ✅ Limited
Percentage rollout ✅ Deterministic ✅ ✅ ❌
Country targeting ✅ ✅ (paid) ✅ (enterprise) ❌
A/B testing variants ✅ ✅ (paid) ✅ ❌
Context propagation ✅ ✅ ✅ ❌
Live config reload ✅ File watching ✅ Streaming ✅ Polling ✅
Thread-safe ✅ ✅ ✅ ✅
Pricing Free (MIT) $10–$500+/mo Free (OSS) / Enterprise Free
Python-only ✅ Multi-language Multi-language Python (Django)

toggleflag fills a gap: a zero-dependency, pure-Python feature flag library that supports sophisticated targeting rules without requiring any infrastructure.


Installation

pip install toggleflag

That's it. No servers to configure. No databases to provision. No accounts to create.


Quick Start

Boolean Flags

import toggle

flags = toggle.Flags()

flags.define("dark_mode", default=True)
flags.define("maintenance_mode", default=False)

flags.is_on("dark_mode")          # True
flags.is_off("maintenance_mode")  # True

Percentage Rollout

flags.define("new_checkout", default=False, rollout_pct=50)

# Deterministic: same user always gets the same result
flags.is_on("new_checkout", user_id="alice")   # True (consistent)
flags.is_on("new_checkout", user_id="bob")     # False (consistent)

A/B Testing with Variants

flags.define("pricing_page", variants=["control", "variant_a", "variant_b"], rollout_pct=40)

# Returns a variant string, or None if the user is outside the rollout
flags.variant("pricing_page", user_id="u1")  # "variant_a"
flags.variant("pricing_page", user_id="u2")  # None (outside 40% rollout)

Per-User Targeting

# Beta access for specific users
flags.define("beta_features", default=False, user_ids=["alice", "bob", "charlie"])

flags.is_on("beta_features", user_id="alice")   # True
flags.is_on("beta_features", user_id="dave")    # False

# Per-user overrides (force on/off regardless of other rules)
flags.define("experiment", default=False, rollout_pct=10,
             overrides={"alice": True, "bob": False})

flags.is_on("experiment", user_id="alice")  # True (forced on)
flags.is_on("experiment", user_id="bob")    # False (forced off)

Country Targeting

flags.define("gdpr_consent_flow", default=True, countries=["DE", "FR", "GB"])

flags.is_on("gdpr_consent_flow", country="US")  # False
flags.is_on("gdpr_consent_flow", country="DE")  # True

Context Propagation

with flags.context(user_id="alice", country="US"):
    # All flag checks within this block use alice/US context
    flags.is_on("beta_features")      # True (alice is in user_ids)
    flags.is_on("us_only_promo")      # True (US country match)
    flags.variant("experiment_v2")    # Deterministic variant for alice

    # Nested contexts merge with parent
    with flags.context(country="DE"):
        flags.is_on("gdpr_consent_flow")  # True (DE, alice's user_id preserved)

# Explicit arguments override context
with flags.context(user_id="alice"):
    flags.is_on("beta_features", user_id="bob")  # False (explicit arg wins)

File-Based Configuration

{
  "new_checkout": {"default": true, "rollout_pct": 50},
  "dark_mode": {"default": true},
  "experiment_v2": {
    "variants": ["control", "treatment"],
    "rollout_pct": 30,
    "countries": ["US", "GB"]
  },
  "beta_features": {
    "default": false,
    "user_ids": ["alice", "bob"]
  }
}
flags = toggle.Flags()
flags.load_file("flags.json")

Environment Variables

export TOGGLE_DARK_MODE=true
export TOGGLE_NEW_CHECKOUT=50
export TOGGLE_EXPERIMENT=50:treatment,control
flags = toggle.Flags()
flags.load_env()

# Custom prefix
flags.load_env(prefix="APP_")  # reads APP_DARK_MODE=true, etc.

Auto-Reload File Changes

watcher = flags.watch("flags.json", poll_interval=2.0)

# In your request handler or background loop:
if watcher.changed():
    flags.load(watcher.read())
    print("Flags reloaded!")

Deterministic Rollout

Percentage-based targeting in toggleflag is deterministic — the same user always gets the same result, across requests, servers, and restarts.

This works by computing MD5(flag_name + ":" + user_id) % 100 and comparing against rollout_pct. The result depends only on the flag name and user identifier, not on random state.

flags.define("experiment", variants=["control", "treatment"], rollout_pct=30)

# These calls always return the same value for the same user
flags.variant("experiment", user_id="alice")  # Always "treatment"
flags.variant("experiment", user_id="bob")    # Always None

Why MD5? MD5 is fast (important for request-time evaluation), uniformly distributed, and deterministic. While not cryptographically secure, that's irrelevant here — we're bucketing users, not protecting secrets.

No user ID? When no user identifier is provided, the system uses a default sentinel value, meaning all anonymous users get the same result. For meaningful rollouts, always provide a user_id.


API Reference

Flags()

Create a new flag engine instance. Thread-safe.

flags.define(name, *, default=False, rollout_pct=?, variants=None, user_ids=None, countries=None, overrides=None)

Define a feature flag.

Parameter Type Default Description
name str — Flag identifier
default bool False Default on/off state (used when no rollout_pct is set)
rollout_pct int auto 0–100+ percentage of users who see the flag
variants list[str] [] Variant names for A/B testing
user_ids list[str] None Allow-list of user IDs
countries list[str] None Allow-list of country codes
overrides dict {} Per-user forced values {"user_id": True/False/"variant"}

flags.is_on(name, *, user_id=None, country=None) → bool

Check if a flag is on for the given context.

flags.is_off(name, **kwargs) → bool

Inverse of is_on.

flags.variant(name, *, user_id=None, country=None) → str | None

Return the A/B variant for the user, or None if outside rollout.

flags.context(*, user_id=None, country=None, **extra) → ContextManager

Push a temporary evaluation context. Nestable — child contexts merge with parents.

flags.load(data: dict) / flags.load_file(path) / flags.load_env(prefix="TOGGLE_")

Bulk-load flag definitions from a dict, JSON/YAML file, or environment variables.

flags.watch(path, poll_interval=2.0) → FileWatcher

Create a file watcher for live config reloading.

flags.list_flags() → list[str] / flags.get(name) → FlagDef | None / flags.undefine(name) / flags.clear()

Introspection and management methods.


Design Philosophy

  1. Zero dependencies. Pure Python 3.10+. No external packages. No database. No network calls. pip install and go.

  2. Deterministic by default. Same user, same flag, same result — always. No random state to corrupt or drift.

  3. Configuration flexibility. Programmatic API, JSON/YAML files, environment variables, or any combination. Use what fits your deployment.

  4. Targeting composure. User IDs, countries, percentages, and overrides compose naturally. Each rule is a filter; they stack.

  5. No infrastructure required. Unlike LaunchDarkly or Unleash, toggleflag needs no server, database, or cluster. A JSON file is sufficient for production use.

  6. Thread-safe. All flag reads and writes are protected by locks. Safe for multi-threaded web servers (Django, Flask, FastAPI).

  7. Explicit is better than implicit. Flags must be defined before use. Undefined flags return False rather than raising errors — fail soft in production.


Use Cases

A/B Testing

flags.define("pricing_page", variants=["control", "variant_a", "variant_b"], rollout_pct=50)

variant = flags.variant("pricing_page", user_id=current_user.id)
if variant == "variant_a":
    return render_pricing_v2(request)
return render_pricing(request)

Phased Rollout

# Week 1: 10% of users
flags.define("new_api", default=False, rollout_pct=10)

# Week 2: Increase to 50%
flags.define("new_api", default=False, rollout_pct=50)

# Week 3: Full launch
flags.define("new_api", default=True)

Canary Releases

flags.define("v2_engine", default=False, user_ids=["alice", "bob", "qa_team"])

if flags.is_on("v2_engine", user_id=request.user.id):
    return v2_handler(request)
return v1_handler(request)

Kill Switches

# Disable a feature instantly without redeploying
flags.define("external_payments", default=True)

# In a config file — flip to false to disable
# {"external_payments": {"default": false}}

Country-Specific Features

flags.define("eu_compliance", default=True, countries=["DE", "FR", "IT", "ES", "GB"])
flags.define("us_tax_form", default=True, countries=["US"])

Beta Programs

flags.define("beta_ai_features", default=False, user_ids=beta_users)

Testing

pip install -e ".[dev]"
pytest tests/ -v

87 tests covering all targeting rules, context propagation, file/env loading, thread safety, and edge cases.


License

MIT © Ravi Teja Prabhala Venkata

Metadata

Release files for toggleflag 0.3.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for toggleflag 0.3.0
File Size Uploaded Provenance Actions
toggleflag-0.3.0.tar.gz 15.9 kB Without provenance Details for toggleflag-0.3.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for toggleflag 0.3.0
File Interpreter ABI Platform Actions
toggleflag-0.3.0-py3-none-any.whl Python 3 none any Details for toggleflag-0.3.0-py3-none-any.whl

Total release size: 25.9 kB

Release files / toggleflag-0.3.0.tar.gz

Download URL toggleflag-0.3.0.tar.gz
Size 15.9 kB
Tags Source
SHA-256 checksum
How to use checksums
3a4c595adbcaa3fbcf9c1755bff83831890b550943711add05db2b1b5c929f70
BLAKE2b-256 checksum
How to use checksums
e8ff33c9e1fee9b0bda27399b47a6591e38fb0322b3561d0551d986f635f8d75
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.11.5

Release files / toggleflag-0.3.0-py3-none-any.whl

Download URL toggleflag-0.3.0-py3-none-any.whl
Size 10.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
1c8adb03ec3280d2dcf40b1f1ab70b937f9403db674ddad63aa40c0d5ec1ce80
BLAKE2b-256 checksum
How to use checksums
25d4b89f579e9db41ef47a631beaeb6ea6b4ad68308406e6306b9a442bf20796
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.11.5

Security for toggleflag 0.3.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.3.0 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page