Feature Flags Extra Light - A lightweight, file-based feature flag system with gradual rollout support
Project description
FFXL-P: Feature Flags Extra Light - Python
A lightweight, file-based feature flag system for Python applications. This is a Python implementation inspired by the ffxl TypeScript library.
Features
- Gradual rollout - Enable features for a percentage of users (10%, 50%, 100%, etc.)
- Environment-based control - Different rollout percentages per environment
- User-specific feature flag access control
- Combined environment + user restrictions
- YAML-based configuration stored locally
Installation
Install from PyPI:
pip install ffxl-p
uv add ffxl-p
Quick Start
1. Create a configuration file
Create feature-flags.yaml:
features:
new_dashboard:
enabled: true
comment: "New dashboard UI"
beta_feature:
enabled: false
comment: "Beta feature - not ready yet"
admin_panel:
onlyForUserIds:
- "user-123"
- "user-456"
comment: "Admin panel - restricted access"
allow_mock_payments:
enabled: true
environments: ["dev"]
comment: "Mock payments only in development"
experimental_feature:
rollout:
dev: 100
production: 5
comment: "Cautious rollout - 5% in production"
2. Use in your code
from ffxl_p import load_feature_flags, is_feature_enabled, User
# Load configuration
load_feature_flags(environment='production') # or get name of your environment from env variables
# Check global feature
if is_feature_enabled('new_dashboard'):
print("Show new dashboard")
# Check user-specific feature
user = "user-123"
if is_feature_enabled('admin_panel', user):
print("Show admin panel")
# Or use a dict
if is_feature_enabled('admin_panel', 'user-456'):
print("Show admin panel")
Configuration Format
Global Enabled/Disabled
features:
feature_name:
enabled: true # or false
comment: "Optional description"
User-Specific Access
features:
feature_name:
onlyForUserIds:
- "user-1"
- "user-2"
comment: "Only for specific users"
When onlyForUserIds is present and populated, it takes precedence over the enabled setting.
Environment-Based Access
Control which environments a feature is enabled in:
features:
debug_mode:
enabled: true
environments: ["dev"]
comment: "Only in development"
staging_feature:
enabled: true
environments: ["dev", "staging"]
comment: "Dev and staging only"
production_feature:
enabled: true
environments: ["production"]
comment: "Production only"
Gradual Rollout (Percentage-Based)
Enable features for a percentage of users in each environment:
features:
new_feature:
rollout:
dev: 100 # 100% in dev
staging: 50 # 50% in staging
production: 10 # 10% in production
comment: "Gradual rollout - start small, expand carefully"
experimental_feature:
rollout:
dev: 100
staging: 25
production: 5
comment: "Cautious rollout - 5% in production"
How it works:
- Uses consistent hashing (SHA256) of
feature_name + user_id - Same user always gets same result for same feature
- Different features have independent distributions
- Requires user - percentage rollout only works when a user is provided
Example usage:
from ffxl_p import load_feature_flags, is_feature_enabled, User
load_feature_flags(environment='production') # or get name of your environment from env variables
user = "user-123"
if is_feature_enabled('new_feature', user):
# This user is in the 10% rollout group
show_new_feature()
Combined Restrictions
Combine environment restrictions with percentage rollout:
features:
advanced_feature:
environments: ["staging", "production"]
rollout:
staging: 100 # All users in staging
production: 25 # 25% of users in production
comment: "Full staging test, then 25% production rollout"
Priority Order:
- Environment check (if
environmentsis specified) - Percentage rollout (if
rolloutis specified, requires user) - User-specific list (if
onlyForUserIdsis specified) - Global
enabledflag
API Reference
Loading Configuration
# Load from default location (./feature-flags.yaml)
load_feature_flags()
# Load with explicit environment
load_feature_flags(environment='production')
# Load from custom path
load_feature_flags('./config/flags.yaml', environment='staging')
# Load as JSON string (for environment variables)
config_string = load_feature_flags_as_string()
Feature Checks
# Check single feature
is_feature_enabled('feature_name', user)
# Check if ANY features are enabled
is_any_feature_enabled(['feature1', 'feature2'], user)
# Check if ALL features are enabled
are_all_features_enabled(['feature1', 'feature2'], user)
Getting Features
# Get all enabled features for a user
enabled = get_enabled_features(user) # Returns: ['feature1', 'feature2']
# Get multiple feature flags as dict
flags = get_feature_flags(['feature1', 'feature2'], user)
# Returns: {'feature1': True, 'feature2': False}
Utility Functions
# Check if feature exists
feature_exists('feature_name')
# Get all feature names
get_all_feature_names()
# Get feature configuration
get_feature_config('feature_name')
Environment Variables
Custom Configuration File
# Option 1
export FFXL_FILE=./config/flags.yaml
# Option 2
export FEATURE_FLAGS_FILE=./config/flags.yaml
Configuration via Environment
# Pass configuration as JSON string
export FFXL_CONFIG='{"features": {"feature1": {"enabled": true}}}'
Set Current Environment
# Option 1: Use FFXL_ENV
export FFXL_ENV=production
# Option 2: Use generic ENV variable
export ENV=staging
# In code (takes precedence over env variables)
load_feature_flags(environment='dev')
Development Mode
Enable detailed logging:
export FFXL_DEV_MODE=true
Framework Integration
Flask
from flask import Flask
from ffxl_p import load_feature_flags, is_feature_enabled
import os
app = Flask(__name__)
# Load at startup
config = load_feature_flags()
app.config['FFXL_CONFIG'] = config
@app.route('/')
def index():
user = session.get('user_id')
if is_feature_enabled('new_ui', user):
return render_template('new_index.html')
return render_template('old_index.html')
Django
# settings.py
from ffxl_p import load_feature_flags
FEATURE_FLAGS = load_feature_flags('./feature-flags.yaml')
# In views or middleware
from django.conf import settings
from ffxl_p import is_feature_enabled
def my_view(request):
if is_feature_enabled('new_feature', request.user.id):
# Use new feature
pass
FastAPI
from fastapi import FastAPI, Depends
from ffxl_p import load_feature_flags, is_feature_enabled
app = FastAPI()
# Load at startup
@app.on_event("startup")
async def startup_event():
load_feature_flags()
@app.get("/dashboard")
async def dashboard(user_id: str): # it's just an example, validate provided user_id properly in real code
if is_feature_enabled('new_dashboard', user_id):
return {"version": "new"}
return {"version": "old"}
Examples
See example.py for comprehensive usage examples:
python example.py
Development
Run with development mode for detailed logging:
FFXL_DEV_MODE=true python example.py
License
This is a Python port inspired by ffxl. Please check the original repository for license information.
Contributing
Contributions are welcome! Please feel free to submit issues or pull requests.
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 ffxl_p-0.1.0a0.tar.gz.
File metadata
- Download URL: ffxl_p-0.1.0a0.tar.gz
- Upload date:
- Size: 59.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f24a0b01f370b058a6d37296f23d919551c2e6017bbf286294fe6db31bb17c3f
|
|
| MD5 |
efabf2ff45205ea04e9e16102c1de7f3
|
|
| BLAKE2b-256 |
02978b67ea9aa3a69c849f80e191a26ed4c14f7f912264fd919f6b72d6d0bfc6
|
Provenance
The following attestation bundles were made for ffxl_p-0.1.0a0.tar.gz:
Publisher:
publish.yml on emilskra/ffxl-p
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ffxl_p-0.1.0a0.tar.gz -
Subject digest:
f24a0b01f370b058a6d37296f23d919551c2e6017bbf286294fe6db31bb17c3f - Sigstore transparency entry: 637744474
- Sigstore integration time:
-
Permalink:
emilskra/ffxl-p@0e9392ace6ba4e2172967c78fffa58c5a3036833 -
Branch / Tag:
refs/tags/0.1.0a0 - Owner: https://github.com/emilskra
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@0e9392ace6ba4e2172967c78fffa58c5a3036833 -
Trigger Event:
release
-
Statement type:
File details
Details for the file ffxl_p-0.1.0a0-py3-none-any.whl.
File metadata
- Download URL: ffxl_p-0.1.0a0-py3-none-any.whl
- Upload date:
- Size: 12.7 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 |
091bf0dc5f93e04bc6fc5850b8ce43ee8792f388f39c91ca0ee332547bc7ad1c
|
|
| MD5 |
2b37e0141ebaeb8dea4ca69b28599baa
|
|
| BLAKE2b-256 |
bd22acea430feb8de229bb3691d3f5e3dd09ca918a496eb211640d8f2308e269
|
Provenance
The following attestation bundles were made for ffxl_p-0.1.0a0-py3-none-any.whl:
Publisher:
publish.yml on emilskra/ffxl-p
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ffxl_p-0.1.0a0-py3-none-any.whl -
Subject digest:
091bf0dc5f93e04bc6fc5850b8ce43ee8792f388f39c91ca0ee332547bc7ad1c - Sigstore transparency entry: 637744479
- Sigstore integration time:
-
Permalink:
emilskra/ffxl-p@0e9392ace6ba4e2172967c78fffa58c5a3036833 -
Branch / Tag:
refs/tags/0.1.0a0 - Owner: https://github.com/emilskra
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@0e9392ace6ba4e2172967c78fffa58c5a3036833 -
Trigger Event:
release
-
Statement type: