Enterprise multi-tenant feature flag library for Django and FastAPI
Project description
FlagForge
Enterprise Multi-Tenant Feature Flag Control Plane for Python
FlagForge is a production-ready feature flag library for Python with first-class support for Django and FastAPI. It supports multi-tenancy, gradual rollouts, user/group targeting, environment gating, and pluggable storage/cache backends.
Features
- Multi-Tenant — Per-tenant flag overrides; column, schema, or hybrid tenancy (Hybrid Gold Standard)
- Gradual Rollout — Deterministic percentage-based bucketing via MurmurHash3
- User & Group Targeting — Explicitly enable flags for individual users or groups
- Environment Gates — Restrict flags to
staging,production, etc. - Pluggable Storage — Django ORM, SQLAlchemy (async), or in-memory YAML
- Caching — Request-scoped local cache or distributed Redis cache
- Django Integration — App config, migrations, REST API, admin, template tags, decorators
- FastAPI Integration — Async engine, lifespan setup, dependency injection
- REST API — Expose flags to frontends (React, Vue, mobile) via HTTP
- CLI —
flagforge sync / status / enable / disable - Type-safe — Full type hints and
py.typedmarker
Table of Contents
- Installation
- Quick Start
- Django Integration
- FastAPI Integration
- React / Frontend Integration
- Flag Configuration
- Caching
- CLI Reference
- API Reference
- Contributing
Installation
# Core library only
pip install flagforge
# Django support (adds Django ORM storage + DRF REST API)
pip install flagforge[django]
# FastAPI support (adds async SQLAlchemy storage)
pip install flagforge[fastapi]
# Redis cache backend
pip install flagforge[redis]
# Everything
pip install flagforge[all]
Quick Start
from flagforge import FlagEngine, FeatureContext
from flagforge.storage.memory import InMemoryStorage
from flagforge.cache import LocalCache
from flagforge.core.models import FlagDefinition
# 1. Create storage and engine
storage = InMemoryStorage()
engine = FlagEngine(storage=storage, cache=LocalCache())
# 2. Define a flag
storage.upsert_definition(FlagDefinition(
key="new_dashboard",
name="New Dashboard",
default_enabled=False,
))
# 3. Evaluate
context = FeatureContext(tenant_id="acme", user_id="user_42")
if engine.is_enabled("new_dashboard", context):
print("Show new dashboard")
Async (FastAPI / asyncio)
from flagforge import AsyncFlagEngine, FeatureContext
from flagforge.storage.memory import AsyncInMemoryStorage
from flagforge.cache import AsyncLocalCache
engine = AsyncFlagEngine(storage=AsyncInMemoryStorage(), cache=AsyncLocalCache())
ctx = FeatureContext(tenant_id="acme", user_id="user_42")
enabled = await engine.is_enabled("new_dashboard", ctx)
Global Engine Pattern
import flagforge
from flagforge.storage.memory import InMemoryStorage
flagforge.configure_engine(flagforge.FlagEngine(storage=InMemoryStorage()))
# Anywhere in your app:
enabled = flagforge.is_enabled("my_flag", context)
Django Integration
See the Django Guide for the full walkthrough.
1. Install & add to INSTALLED_APPS
pip install flagforge[django]
# settings.py
INSTALLED_APPS = [
...
"flagforge.contrib.django",
]
MIDDLEWARE = [
...
"flagforge.contrib.django.middleware.RequestCacheMiddleware",
]
# Tenancy mode: "column" (default), "schema", or "hybrid"
FLAGFORGE_TENANCY_MODE = "column"
# Cache backend: "local" (default), "redis", "none", or dotted.path.ClassName
FLAGFORGE_CACHE_BACKEND = "local"
2. Run migrations
python manage.py migrate
3. Include the REST API (optional)
# urls.py
from django.urls import path, include
urlpatterns = [
...
path("api/flags/", include("flagforge.contrib.django.urls")),
]
4. Use in views
from flagforge.contrib.django.engine import flag_enabled
def my_view(request):
if flag_enabled("new_checkout", request):
return render(request, "checkout_v2.html")
return render(request, "checkout.html")
5. Use the decorator
from flagforge.contrib.django.decorators import flag_required
@flag_required("beta_dashboard", redirect_to="/")
def beta_view(request):
...
6. Template tags
{% load flagforge %}
{% flag "new_dashboard" request %}
<p>New dashboard is active!</p>
{% endflag %}
7. Sync flags from YAML
python manage.py sync_feature_flags --config config/feature-flags.yaml
FastAPI Integration
See the FastAPI Guide for the full walkthrough.
from fastapi import FastAPI
from flagforge.contrib.fastapi import create_flagforge_lifespan
from flagforge.contrib.fastapi.router import router as flags_router
app = FastAPI(
lifespan=create_flagforge_lifespan(
database_url="postgresql+asyncpg://user:pass@localhost/mydb" # pragma: allowlist secret
)
)
app.include_router(flags_router, prefix="/api")
Dependency injection
from fastapi import Depends
from flagforge.contrib.fastapi.dependencies import feature_flag_dependency
@app.get("/new-feature")
async def new_feature(_=Depends(feature_flag_dependency("new_feature"))):
return {"message": "New feature!"}
React / Frontend Integration
See the React Guide for components, hooks, and usage patterns.
FlagForge exposes a REST endpoint (GET /api/flags/) that returns all resolved flag values for the current user. Your frontend can fetch these once and use them throughout the app.
GET /api/flags/
Authorization: Bearer <token>
{
"new_dashboard": true,
"beta_checkout": false,
"dark_mode": true
}
Quick React example:
import { useFlags } from "./hooks/useFlags";
function App() {
const { flags, loading } = useFlags("/api/flags/");
if (loading) return <Spinner />;
return flags.new_dashboard ? <NewDashboard /> : <OldDashboard />;
}
Flag Configuration
YAML file (config/feature-flags.yaml)
flags:
new_dashboard:
name: New Dashboard
description: Redesigned dashboard UI
default_enabled: false
is_public: true
rollout_percentage: 0
environments:
- staging
- production
beta_checkout:
name: Beta Checkout Flow
description: New one-page checkout
default_enabled: false
is_public: false
rollout_percentage: 20 # 20% gradual rollout
dark_mode:
name: Dark Mode
description: Dark theme for all users
default_enabled: true
is_public: true
Flag fields
| Field | Type | Default | Description |
|---|---|---|---|
key |
string | — | Unique identifier |
name |
string | — | Human-readable name |
description |
string | "" |
Optional description |
default_enabled |
bool | false |
Default state |
is_public |
bool | false |
Exposed to unauthenticated users |
rollout_percentage |
int 0–100 | 0 |
Gradual rollout percentage |
deprecated |
bool | false |
Mark as deprecated |
environments |
list[str] | null |
Restrict to environments |
Resolution Priority
- Environment gate — if
environmentsset and current env not in list →False - User targeting — if user is in tenant override's
enabled_for_users→True - Group targeting — if user's group is in
enabled_for_groups→True - Tenant override — explicit
enabled: true/falseon tenant - Default + rollout —
default_enabledwith optionalrollout_percentage
Caching
| Backend | Class | Use Case |
|---|---|---|
| Local (in-memory) | LocalCache / AsyncLocalCache |
Single-process, request-scoped |
| Redis | RedisCache / AsyncRedisCache |
Multi-process, distributed |
| Null | NullCache / AsyncNullCache |
Testing / disable caching |
Configure via Django settings — no custom engine setup needed:
# settings.py
FLAGFORGE_CACHE_BACKEND = "redis"
FLAGFORGE_REDIS_URL = "redis://localhost:6379/0"
FLAGFORGE_CACHE_TTL = 300 # seconds
Or pass directly:
from flagforge.cache import RedisCache
cache = RedisCache(host="localhost", port=6379, db=0, default_ttl=300)
engine = FlagEngine(storage=storage, cache=cache)
CLI Reference
# Sync flag definitions from YAML to database
flagforge sync --config config/feature-flags.yaml
# Show flag status for a tenant
flagforge status --tenant acme_corp
# Enable a flag for a tenant
flagforge enable --flag new_dashboard --tenant acme_corp
# Disable a flag for a tenant
flagforge disable --flag new_dashboard --tenant acme_corp
Django management commands
python manage.py sync_feature_flags --config config/feature-flags.yaml
python manage.py show_flag_status --tenant acme_corp
python manage.py enable_flag_for_tenant --flag new_dashboard --tenant acme_corp
python manage.py disable_flag_for_tenant --flag new_dashboard --tenant acme_corp
API Reference
FlagEngine
engine = FlagEngine(storage, cache=None)
engine.is_enabled(key: str, context: FeatureContext) -> bool
engine.evaluate_many(keys: list[str], context: FeatureContext) -> dict[str, bool]
engine.evaluate_all(context: FeatureContext) -> dict[str, bool]
AsyncFlagEngine
Same interface but all methods are async.
FeatureContext
FeatureContext(
tenant_id: str, # Required — tenant identifier
user_id: str | None, # Optional — user identifier
group_ids: list[str], # Optional — user's groups
environment: str | None, # Optional — e.g. "production"
attributes: dict, # Optional — custom attributes
)
REST Endpoints (Django & FastAPI)
| Method | Path | Auth | Description |
|---|---|---|---|
GET |
/api/flags/ |
Optional | Get all resolved flags for current user |
GET |
/api/flags/admin/ |
Admin | List all flag definitions |
POST |
/api/flags/admin/ |
Admin | Create flag definition |
PUT |
/api/flags/admin/{key}/ |
Admin | Update flag definition |
DELETE |
/api/flags/admin/{key}/ |
Admin | Delete flag definition |
PUT |
/api/flags/admin/{key}/tenants/{tenant_id}/ |
Admin | Upsert tenant override |
DELETE |
/api/flags/admin/{key}/tenants/{tenant_id}/ |
Admin | Delete tenant override |
Contributing
# Clone and set up
git clone https://github.com/flagforge/flagforge
cd flagforge
python -m venv venv && source venv/bin/activate
pip install -e ".[dev,django,fastapi,redis]"
# Run tests
pytest
# Lint
ruff check src/
mypy src/
Pull requests are welcome. Please open an issue first to discuss major changes.
License
MIT — see LICENSE
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 flagforge-1.0.0.tar.gz.
File metadata
- Download URL: flagforge-1.0.0.tar.gz
- Upload date:
- Size: 75.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8765cbcdb07644f13ce8cb30c06a4c5f7b4642e2f6417fe5715bb82df35f6fd0
|
|
| MD5 |
6107a48d7b5a926d92722679d588148b
|
|
| BLAKE2b-256 |
cbdd33562ec6fcf3a46b995d3182a6510de4d37f0e9b8497d975f7bea17193ab
|
Provenance
The following attestation bundles were made for flagforge-1.0.0.tar.gz:
Publisher:
ci.yml on nayanjaiswal1/flagforge-feature-flags
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
flagforge-1.0.0.tar.gz -
Subject digest:
8765cbcdb07644f13ce8cb30c06a4c5f7b4642e2f6417fe5715bb82df35f6fd0 - Sigstore transparency entry: 1042601105
- Sigstore integration time:
-
Permalink:
nayanjaiswal1/flagforge-feature-flags@ec3d5dc00e50063c5cac14b57cc340dbdc6c2ce7 -
Branch / Tag:
refs/tags/v1.0.0 - Owner: https://github.com/nayanjaiswal1
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
ci.yml@ec3d5dc00e50063c5cac14b57cc340dbdc6c2ce7 -
Trigger Event:
push
-
Statement type:
File details
Details for the file flagforge-1.0.0-py3-none-any.whl.
File metadata
- Download URL: flagforge-1.0.0-py3-none-any.whl
- Upload date:
- Size: 57.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
de33831441996ee840a62b89fc04f7879161ac8f4a29b8c57b9e9a6f25fff62b
|
|
| MD5 |
e0e513f1549fce2fcbcaa12fc2f6debf
|
|
| BLAKE2b-256 |
e6266c6af4675e5acd60aa6bec7b76cdb3fce48e2346daa86b8a43ffedb5610f
|
Provenance
The following attestation bundles were made for flagforge-1.0.0-py3-none-any.whl:
Publisher:
ci.yml on nayanjaiswal1/flagforge-feature-flags
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
flagforge-1.0.0-py3-none-any.whl -
Subject digest:
de33831441996ee840a62b89fc04f7879161ac8f4a29b8c57b9e9a6f25fff62b - Sigstore transparency entry: 1042601107
- Sigstore integration time:
-
Permalink:
nayanjaiswal1/flagforge-feature-flags@ec3d5dc00e50063c5cac14b57cc340dbdc6c2ce7 -
Branch / Tag:
refs/tags/v1.0.0 - Owner: https://github.com/nayanjaiswal1
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
ci.yml@ec3d5dc00e50063c5cac14b57cc340dbdc6c2ce7 -
Trigger Event:
push
-
Statement type: