Skip to main content

Feature Flags Extra Light - A lightweight, file-based feature flag system with gradual rollout support

Project description

FFXL-P: Feature Flags Extra Light - Python

PyPI version Python Versions CI License: MIT

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:

  1. Environment check (if environments is specified)
  2. Percentage rollout (if rollout is specified, requires user)
  3. User-specific list (if onlyForUserIds is specified)
  4. Global enabled flag

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


Download files

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

Source Distribution

ffxl_p-0.1.0a0.tar.gz (59.9 kB view details)

Uploaded Source

Built Distribution

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

ffxl_p-0.1.0a0-py3-none-any.whl (12.7 kB view details)

Uploaded Python 3

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

Hashes for ffxl_p-0.1.0a0.tar.gz
Algorithm Hash digest
SHA256 f24a0b01f370b058a6d37296f23d919551c2e6017bbf286294fe6db31bb17c3f
MD5 efabf2ff45205ea04e9e16102c1de7f3
BLAKE2b-256 02978b67ea9aa3a69c849f80e191a26ed4c14f7f912264fd919f6b72d6d0bfc6

See more details on using hashes here.

Provenance

The following attestation bundles were made for ffxl_p-0.1.0a0.tar.gz:

Publisher: publish.yml on emilskra/ffxl-p

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

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

Hashes for ffxl_p-0.1.0a0-py3-none-any.whl
Algorithm Hash digest
SHA256 091bf0dc5f93e04bc6fc5850b8ce43ee8792f388f39c91ca0ee332547bc7ad1c
MD5 2b37e0141ebaeb8dea4ca69b28599baa
BLAKE2b-256 bd22acea430feb8de229bb3691d3f5e3dd09ca918a496eb211640d8f2308e269

See more details on using hashes here.

Provenance

The following attestation bundles were made for ffxl_p-0.1.0a0-py3-none-any.whl:

Publisher: publish.yml on emilskra/ffxl-p

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