Skip to main content

Reusable feature flag infrastructure for Django and Python projects (Plug and Play).

Project description

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:
    • FeatureFlag model
    • 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.py inside 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:

  1. If staff_only=True, only staff users receive the feature.
  2. If enabled=False, feature is off.
  3. If rollout_percentage >= 100, feature is on.
  4. If user is explicitly allowlisted in FeatureFlag.users, feature is on.
  5. If rollout_percentage <= 0, feature is off.
  6. Otherwise, deterministic rollout is applied by hashing "{flag}:{user_id}" into bucket 0..99.

Enabled/disabled behavior

  • enabled=False always disables (except staff_only is checked first, which still requires staff).
  • enabled=True allows further checks (staff-only, allowlist, rollout).

staff_only behavior

  • staff_only=True returns True only when user.is_staff is 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 True even when rollout_percentage=0.
  • staff_only=True still 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 True from 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: 30 seconds (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_only for 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.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

feature_flag_infra-0.2.1.tar.gz (12.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

feature_flag_infra-0.2.1-py3-none-any.whl (11.2 kB view details)

Uploaded Python 3

File details

Details for the file feature_flag_infra-0.2.1.tar.gz.

File metadata

  • Download URL: feature_flag_infra-0.2.1.tar.gz
  • Upload date:
  • Size: 12.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for feature_flag_infra-0.2.1.tar.gz
Algorithm Hash digest
SHA256 a50b395f64538aa8270a73846e5494da675cea3609cb162651f67c74ba49e92a
MD5 6021c648c0bf387f50225e3c454ab428
BLAKE2b-256 b12b55078f9546811a1f0ae7fcd34240c2d2bac20a3f562e988d09a83914c09a

See more details on using hashes here.

Provenance

The following attestation bundles were made for feature_flag_infra-0.2.1.tar.gz:

Publisher: cd.yml on 0FFSIDE1/feature_flag_infra

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file feature_flag_infra-0.2.1-py3-none-any.whl.

File metadata

File hashes

Hashes for feature_flag_infra-0.2.1-py3-none-any.whl
Algorithm Hash digest
SHA256 1b77cf55d8dade61aae7a6408c2e6dc5dc82e340847d8aa478256017378634df
MD5 bf7532d18ac4b3fadbe100d894a68507
BLAKE2b-256 d230ab52bb16f35d22c2361744b9512433a1f469d70adcb28ff28dec24291318

See more details on using hashes here.

Provenance

The following attestation bundles were made for feature_flag_infra-0.2.1-py3-none-any.whl:

Publisher: cd.yml on 0FFSIDE1/feature_flag_infra

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page