feature-flag-infra
feature-flag-infra is a reusable Python/Django feature flag infrastructure package for progressive delivery and controlled rollouts.
It is published as:
pip install feature-flag-infra
And imported as:
import feature_flag_infra
Why this package exists
Feature flags are useful in both web apps and background services, but many implementations are tightly coupled to one framework or one storage backend.
This package provides a small, provider-driven core API that works:
- in Django projects (with a built-in database provider and model)
- in non-Django Python projects (by implementing your own provider)
It is infrastructure code intended to be reused across projects, not a standalone Django project.
Features
- Provider abstraction via
FeatureFlagProvider - Core service API via
FeatureFlagService - Deterministic percentage rollout helper (
is_user_in_rollout) - Built-in Django integration:
FeatureFlagmodel- Django DB-backed provider (
DjangoDBFlagProvider) - convenience accessor (
get_feature_flags) - migration and management command (
register_feature)
- Built-in cache usage in Django provider (flag metadata cache)
Installation
pip install feature-flag-infra
Note: This package has a Django dependency and is designed as a reusable library. Do not expect a
manage.pyinside this repository.
Quick start
Use the framework-agnostic service API with a custom provider:
from feature_flag_infra.interfaces import FeatureFlagProvider
from feature_flag_infra.service import FeatureFlagService
class InMemoryProvider(FeatureFlagProvider):
def __init__(self):
self.flags = {
"new_checkout": True,
"beta_search": False,
}
def is_enabled(self, flag, *, user=None, default=False):
return self.flags.get(flag, default)
flags = FeatureFlagService(provider=InMemoryProvider())
if flags.enabled("new_checkout"):
print("New checkout is enabled")
Django usage
1) INSTALLED_APPS setup
Use the package app config:
INSTALLED_APPS = [
# ...
"feature_flag_infra.django.apps.FeatureFlagInfraConfig",
]
- App config path:
feature_flag_infra.django.apps.FeatureFlagInfraConfig - App label:
feature_flag_infra
2) Migrations
This package ships with migrations for the FeatureFlag model.
Run:
python manage.py migrate
You do not need to generate migrations for this package yourself.
3) Creating feature flags
Option A: Django admin / ORM
from feature_flag_infra.django.models import FeatureFlag
FeatureFlag.objects.create(
name="new_invoice_flow",
enabled=True,
rollout_percentage=100,
)
Option B: management command
python manage.py register_feature new_invoice_flow --enable --rollout 100
You can register multiple flags at once:
python manage.py register_feature flag_a flag_b --rollout 50
4) Using get_feature_flags
from feature_flag_infra.django.service import get_feature_flags
flags = get_feature_flags()
if flags.enabled("new_invoice_flow", user=request.user):
# gated behavior
...
5) Example usage in views/services
# views.py
from django.http import JsonResponse
from feature_flag_infra.django.service import get_feature_flags
def checkout_view(request):
flags = get_feature_flags()
if flags.enabled("new_checkout", user=request.user):
return JsonResponse({"flow": "new"})
return JsonResponse({"flow": "legacy"})
# services.py
from feature_flag_infra.django.service import get_feature_flags
def maybe_run_anomaly_detection(user):
flags = get_feature_flags()
if flags.enabled("anomaly_detection", user=user):
return "anomaly detection enabled"
return "anomaly detection disabled"
Non-Django Python usage
FeatureFlagProvider
FeatureFlagProvider is the interface your backend must implement:
class FeatureFlagProvider:
def is_enabled(self, flag: str, *, user=None, default=False) -> bool:
...
Custom in-memory provider
from feature_flag_infra.interfaces import FeatureFlagProvider
class InMemoryProvider(FeatureFlagProvider):
def __init__(self, flags=None):
self.flags = flags or {}
def is_enabled(self, flag, *, user=None, default=False):
return self.flags.get(flag, default)
FeatureFlagService usage
from feature_flag_infra.service import FeatureFlagService
provider = InMemoryProvider({"feature_x": True})
flags = FeatureFlagService(provider)
assert flags.enabled("feature_x") is True
assert flags.enabled("missing", default=False) is False
Rollout behavior
The Django provider evaluates in this order:
- If
staff_only=True, only staff users receive the feature. - If
enabled=False, feature is off. - If
rollout_percentage >= 100, feature is on. - If user is explicitly allowlisted in
FeatureFlag.users, feature is on. - If
rollout_percentage <= 0, feature is off. - Otherwise, deterministic rollout is applied by hashing
"{flag}:{user_id}"into bucket0..99.
Enabled/disabled behavior
enabled=Falsealways disables (exceptstaff_onlyis checked first, which still requires staff).enabled=Trueallows further checks (staff-only, allowlist, rollout).
staff_only behavior
staff_only=TruereturnsTrueonly whenuser.is_staffis truthy.- Anonymous or non-staff users get
False.
Users allowlist behavior
Implemented via FeatureFlag.users many-to-many relation to AUTH_USER_MODEL.
- If a user is allowlisted, they get
Trueeven whenrollout_percentage=0. staff_only=Truestill takes precedence and can block non-staff allowlisted users.
rollout_percentage behavior
0: no rollout users are included.100: all users are included.1-99: deterministic partial rollout using stable hash bucketing.
Anonymous user behavior
- Anonymous/no-user requests can only receive
Truefrom the Django provider when rollout is effectively 100% and other conditions allow it. - Anonymous users do not participate in percentage rollout because no user identifier is available.
Caching behavior
DjangoDBFlagProvider caches flag metadata by key feature_flag:{flag_name} using Django cache.
- Default TTL:
30seconds (CACHE_TTL = 30) - Cached fields:
id,enabled,staff_only,rollout_percentage - User allowlist checks are still evaluated per call (DB existence check for authenticated users)
You can override TTL:
from feature_flag_infra.django.providers import DjangoDBFlagProvider
provider = DjangoDBFlagProvider(cache_ttl=10)
Testing
This repository uses pytest with pytest-django.
Typical commands:
pytest
pytest -q
The test suite covers model behavior, provider behavior (including caching and allowlist), and rollout determinism.
Package structure
feature_flag_infra/
├── interfaces.py # Provider interface
├── service.py # FeatureFlagService
├── rollout.py # Deterministic rollout helpers
└── django/
├── apps.py # Django AppConfig
├── models.py # FeatureFlag model
├── providers.py # DjangoDBFlagProvider
├── service.py # get_feature_flags()
├── management/commands/
│ └── register_feature.py # Feature registration command
└── migrations/
└── 0001_initial.py
Extending with custom providers
To support other storage backends (Redis, remote API, config service), implement FeatureFlagProvider and inject it into FeatureFlagService.
from feature_flag_infra.interfaces import FeatureFlagProvider
from feature_flag_infra.service import FeatureFlagService
class CustomProvider(FeatureFlagProvider):
def is_enabled(self, flag, *, user=None, default=False):
# read from your backend
return default
flags = FeatureFlagService(CustomProvider())
This keeps your application logic independent from storage details.
Security and best practices
- Treat feature flags as control-plane configuration: restrict who can modify them.
- Use
staff_onlyfor internal rollouts and admin-safe experiments. - Prefer gradual rollout (
rollout_percentage) over instant global enablement. - Keep flag names stable and descriptive (e.g.,
new_checkout_v2). - Audit and retire stale flags to reduce long-term branching complexity.
Roadmap
Potential future enhancements (not currently implemented in core package):
- First-class Redis provider implementation
- Central API-backed provider
- CLI improvements for bulk operations
- Optional admin UX enhancements
- Exposure/metrics hooks
License
MIT License. See LICENSE.
Release files for feature-flag-infra 0.2.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| feature_flag_infra-0.2.1.tar.gz | 12.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| feature_flag_infra-0.2.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 23.3 kB
Release files / feature_flag_infra-0.2.1.tar.gz
| Download URL | feature_flag_infra-0.2.1.tar.gz |
|---|---|
| Size | 12.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
a50b395f64538aa8270a73846e5494da675cea3609cb162651f67c74ba49e92a
|
|
BLAKE2b-256 checksum How to use checksums |
b12b55078f9546811a1f0ae7fcd34240c2d2bac20a3f562e988d09a83914c09a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
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.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on May 6, 2026.
Transparency logRelease files / feature_flag_infra-0.2.1-py3-none-any.whl
| Download URL | feature_flag_infra-0.2.1-py3-none-any.whl |
|---|---|
| Size | 11.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
1b77cf55d8dade61aae7a6408c2e6dc5dc82e340847d8aa478256017378634df
|
|
BLAKE2b-256 checksum How to use checksums |
d230ab52bb16f35d22c2361744b9512433a1f469d70adcb28ff28dec24291318
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
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.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on May 6, 2026.
Transparency log