Metadata-Version: 2.4
Name: flagpool-sdk
Version: 0.3.4
Summary: Python SDK for Flagpool feature flags
License: MIT
Project-URL: Homepage, https://flagpool.io
Project-URL: Documentation, https://flagpool.io/docs
Project-URL: Repository, https://github.com/flagpool/flagpool-sdk
Keywords: feature-flags,feature-toggles,flagpool,sdk
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.8
Description-Content-Type: text/markdown
Requires-Dist: requests>=2.25.0
Provides-Extra: dev
Requires-Dist: pytest>=7.0.0; extra == "dev"
Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
Requires-Dist: responses>=0.23.0; extra == "dev"

<p>
  <br>
  <a href="https://www.flagpool.io/en">
    <img src="https://www.flagpool.io/assets/flagpool.svg" alt="Flagpool logo" width="120">
  </a>
  <br>
  <br>
</p>

# Feature Flags for Python - Flagpool Python SDK

`flagpool-sdk` is the official Python SDK for [Flagpool](https://www.flagpool.io/en), a feature flag platform for controlling feature releases without coupling them to code deployments. Use feature flags in Python applications and backend services to enable features for selected users, manage gradual rollouts, and deliver A/B test variants.

Build boolean feature toggles or configure string, number, and JSON flag values with `FlagpoolClient`. Manage your flags in Flagpool, evaluate them locally in your application, and update user context as your users sign in or change plans.

## Contents

- [Why Flagpool for Python?](#why-flagpool-for-python)
- [Installation](#installation)
- [Quick Start](#quick-start)
- [Configuration](#configuration)
- [API Reference](#api-reference)
- [Targeting Rules](#targeting-rules)
- [Dynamic Context Updates](#dynamic-context-updates)
- [Real-time Updates](#real-time-updates)
- [Framework Examples](#framework-examples)
- [Target List Encryption](#target-list-encryption)
- [Analytics (Paid Plans Only)](#analytics-paid-plans-only)
- [Documentation and Support](#documentation-and-support)

## Why Flagpool for Python?

- **Local flag evaluation:** Evaluate downloaded flags in your application without a server request for each flag check.
- **Python client API:** Read boolean flags with `is_enabled`, retrieve configuration values with `get_value`, and update user context with `update_context`.
- **Multiple flag types:** Use boolean feature toggles and string, number, or JSON configuration values.
- **User targeting:** Deliver features based on user attributes and target lists. See the [feature flag targeting guide](https://www.flagpool.io/docs/concepts/targeting).
- **Deterministic rollouts:** Use consistent user context to keep users in the same rollout variation. Follow the [progressive rollout guide](https://www.flagpool.io/docs/guides/progressive-rollouts).
- **A/B variants:** Use flag values to select different application experiences. See the [A/B testing guide](https://www.flagpool.io/docs/guides/ab-testing).
- **Automatic refresh and offline support:** Poll for flag changes and continue evaluating cached flags when the network is unavailable.
- **Minimal dependencies:** The core SDK requires only `requests`; encrypted target lists need a crypto adapter.
- **Optional analytics:** Track flag evaluation counts with opt-in analytics on paid plans.

## Installation

```bash
pip install flagpool-sdk
```

**Requirements:** Python 3.8 or later

## Quick Start

Create a project in [Flagpool](https://www.flagpool.io/en) and obtain your project ID, environment-specific API key, and decryption key from the dashboard. Create the flags used below: `new-dashboard` (boolean), `cta-button-color` (string), `max-upload-size-mb` (number), and `checkout-config` (JSON).

For the full setup walkthrough, follow the [Flagpool quick-start guide](https://www.flagpool.io/docs/getting-started/quickstart). If your environment uses encrypted target lists, configure a crypto adapter as described in [Target List Encryption](#target-list-encryption).

```python
from flagpool_sdk import FlagpoolClient

client = FlagpoolClient(
    project_id='your-project-uuid',    # Get from Flagpool dashboard
    api_key='fp_production_xxx',       # Environment-specific API key
    decryption_key='fp_dec_xxx',       # For CDN URL hash & target lists
    context={
        'userId': 'user-123',
        'email': 'alice@example.com',
        'plan': 'pro',
        'country': 'US'
    }
)

client.init()

# Boolean flag
if client.is_enabled('new-dashboard'):
    show_new_dashboard()

# String flag (A/B test)
button_color = client.get_value('cta-button-color')
# 'blue' | 'green' | 'orange'

# Number flag
max_upload = client.get_value('max-upload-size-mb')
# 10 | 100 | 1000

# JSON flag
config = client.get_value('checkout-config')
# { 'showCoupons': True, 'maxItems': 50, ... }

# Clean up when done
client.close()
```

## Configuration

```python
client = FlagpoolClient(
    # Required
    project_id='your-project-uuid',     # Project ID from dashboard
    api_key='fp_production_xxx',        # Environment-specific API key
    decryption_key='fp_dec_xxx',        # For CDN URL hash & target list decryption

    # Optional
    context={                           # User context for targeting
        'userId': 'user-123',
        'email': 'user@example.com',
        'plan': 'pro',
        # Add any attributes for targeting
    },
    polling_interval=30,                # Auto-refresh interval (seconds), default: 30
    url_override=None,                  # Complete URL override (for self-hosted/testing)
)
```

## API Reference

### Methods

| Method | Description |
|--------|-------------|
| `init()` | Initialize client and fetch flags (required before evaluation) |
| `is_enabled(key)` | Check if a boolean flag is enabled |
| `get_value(key)` | Get flag value (any type) |
| `get_variation(key)` | Alias for get_value |
| `get_all_flags()` | Get all evaluated flag values as dict |
| `update_context(ctx)` | Update user context and re-evaluate flags |
| `on_change(callback)` | Subscribe to flag value changes |
| `close()` | Clean up resources (stop polling) |

### Flag Types

#### Boolean Flags

```python
if client.is_enabled('feature-flag'):
    # Feature is enabled for this user
    pass
```

#### String Flags

Perfect for A/B tests and feature variants:

```python
variant = client.get_value('button-color')
# Returns: 'blue' | 'green' | 'orange'
```

#### Number Flags

Great for limits, thresholds, and configurations:

```python
limit = client.get_value('rate-limit')
# Returns: 100 | 1000 | 10000
```

#### JSON Flags (dict)

For complex configurations:

```python
config = client.get_value('checkout-config')
# Returns: { 'showCoupons': True, 'maxItems': 50, ... }
```

## Targeting Rules

Flagpool supports powerful targeting with 8 operators:

| Operator | Description | Example |
|----------|-------------|---------|
| `eq` | Equals | `plan == "enterprise"` |
| `neq` | Not equals | `plan != "free"` |
| `in` | In list | `country in ["US", "CA"]` |
| `nin` | Not in list | `country not in ["CN", "RU"]` |
| `contains` | String contains | `email contains "@company.com"` |
| `startsWith` | String starts with | `userId startsWith "admin-"` |
| `inTargetList` | In target list | `userId in beta-testers` |
| `notInTargetList` | Not in target list | `userId not in blocked-users` |

## Dynamic Context Updates

Update user context on the fly - flags re-evaluate automatically:

```python
client = FlagpoolClient(
    api_key='fp_prod_xxx',
    environment='production',
    context={'userId': 'user-1', 'plan': 'free'}
)

client.init()

# User on free plan
print(client.get_value('max-upload-size-mb'))  # 10

# User upgrades to pro
client.update_context({'plan': 'pro'})

# Instantly gets pro limits
print(client.get_value('max-upload-size-mb'))  # 100
```

## Real-time Updates

Flags automatically refresh in the background:

```python
client = FlagpoolClient(
    api_key='fp_prod_xxx',
    environment='production',
    context={'userId': 'user-1'},
    polling_interval=30  # Refresh every 30 seconds
)

client.init()

# Listen for flag changes
def on_flag_change(flag_key, new_value):
    print(f'Flag {flag_key} changed to: {new_value}')
    
    # React to changes
    if flag_key == 'maintenance-mode' and new_value:
        show_maintenance_banner()

client.on_change(on_flag_change)
```

## Framework Examples

### Django

```python
# settings.py
FLAGPOOL_API_KEY = 'fp_production_xxx'

# flags.py
from flagpool_sdk import FlagpoolClient
from django.conf import settings

_client = None

def get_flagpool():
    global _client
    if _client is None:
        _client = FlagpoolClient(
            api_key=settings.FLAGPOOL_API_KEY,
            environment='production',
        )
        _client.init()
    return _client

# views.py
from .flags import get_flagpool

def my_view(request):
    flagpool = get_flagpool()
    flagpool.update_context({'userId': request.user.id})
    
    if flagpool.is_enabled('new-checkout'):
        return render(request, 'new_checkout.html')
    return render(request, 'checkout.html')
```

### Flask

```python
from flask import Flask, g
from flagpool_sdk import FlagpoolClient

app = Flask(__name__)

flagpool = FlagpoolClient(
    api_key='fp_production_xxx',
    environment='production',
)
flagpool.init()

@app.before_request
def set_user_context():
    if hasattr(g, 'user'):
        flagpool.update_context({'userId': g.user.id})

@app.route('/dashboard')
def dashboard():
    if flagpool.is_enabled('new-dashboard'):
        return render_template('new_dashboard.html')
    return render_template('dashboard.html')
```

### FastAPI

```python
from fastapi import FastAPI, Depends
from flagpool_sdk import FlagpoolClient

app = FastAPI()

flagpool = FlagpoolClient(
    api_key='fp_production_xxx',
    environment='production',
)

@app.on_event("startup")
async def startup():
    flagpool.init()

@app.on_event("shutdown")
async def shutdown():
    flagpool.close()

@app.get("/api/data")
async def get_data(user_id: str):
    flagpool.update_context({'userId': user_id})
    
    if flagpool.is_enabled('new-api-response'):
        return {"version": "v2", "data": new_data}
    return {"version": "v1", "data": legacy_data}
```

## Target List Encryption

By default, target lists (used for `inTargetList` and `notInTargetList` operators) are encrypted in SDK exports to protect sensitive user data like emails and user IDs.

### Encrypted Target Lists (Default)

If your environment has target list encryption enabled, you must provide a crypto adapter:

```python
from flagpool_sdk import FlagpoolClient, set_crypto_adapter

class MyCryptoAdapter:
    def decrypt(self, ciphertext: str, key: str, iv: str, tag: str) -> str:
        # Implement AES-256-GCM decryption
        # See examples/ for a complete implementation using cryptography library
        return decrypted_text

# Set the adapter before creating the client
set_crypto_adapter(MyCryptoAdapter())

client = FlagpoolClient(
    project_id='your-project-uuid',
    api_key='fp_production_xxx',
    decryption_key='fp_dec_xxx',
    context={'userId': 'user-123'}
)
```

### Error: "Encrypted target lists received but no crypto adapter configured"

If you see this error, it means:

1. **Your environment has target list encryption enabled** (the default)
2. **You haven't set up a crypto adapter**

**Solutions:**

1. **Set up a crypto adapter** (recommended for production):
   ```python
   set_crypto_adapter(MyCryptoAdapter())
   ```

2. **Disable encryption** (for development/testing):
   - Go to Flagpool Dashboard → Settings → Environments
   - Toggle off "Target List Encryption" for your environment
   - This will export target lists in plaintext (values will be visible)

### Plaintext Target Lists

If you disable target list encryption in the dashboard, no crypto adapter is needed. The SDK will use target lists directly without decryption.

> ⚠️ **Security Note:** Disabling encryption exposes target list values (emails, user IDs, etc.) in plaintext in the SDK exports. Only disable for development environments or non-sensitive data.

## Analytics (Paid Plans Only)

Track flag evaluation counts to understand usage patterns. Analytics is **opt-in**, disabled by default, and only available on paid plans.

### Enabling Analytics

```python
from flagpool_sdk import FlagpoolClient, AnalyticsConfig

client = FlagpoolClient(
    project_id="your-project-uuid",
    api_key="your-api-key",
    decryption_key="your-decryption-key",
    analytics=AnalyticsConfig(enabled=True),
)
client.init()
```

### Configuration Options

```python
analytics=AnalyticsConfig(
    enabled=True,
    flush_interval=60,        # Flush interval in seconds (min: 30)
    flush_threshold=100,      # Flush after N evaluations
    sample_rate=1.0,          # Sample rate 0.0–1.0
    sync_flush_on_shutdown=False,  # Block on exit until flushed
)
```

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `enabled` | bool | `False` | Enable/disable analytics |
| `flush_interval` | int | `60` | Flush interval in seconds (minimum: 30) |
| `flush_threshold` | int | `100` | Flush after N evaluations |
| `sample_rate` | float | `1.0` | Sample rate (0.0–1.0) |
| `sync_flush_on_shutdown` | bool | `False` | Flush synchronously on process exit |

### How It Works

- Evaluation counts are batched in memory
- Batches are sent asynchronously (fire-and-forget)
- Analytics never blocks flag evaluation
- Data is aggregated daily in your Flagpool dashboard
- **Note**: Data is silently discarded for free plan projects

### Serverless / Short-Lived Processes

For AWS Lambda or similar short-lived environments, lower the threshold and enable sync flush:

```python
analytics=AnalyticsConfig(
    enabled=True,
    flush_threshold=10,
    sync_flush_on_shutdown=True,
)
```

### Debugging Analytics

```python
# Get current analytics state
state = client.get_analytics_state()
print(state.buffer)         # {'my-flag': 5}
print(state.buffer_size)    # 5
print(state.total_flushes)  # 2

# Get all flags with evaluation status
flags = client.get_all_flags_with_state()
# {'my-flag': {'value': True, 'evaluated': True}, ...}

# Manually flush analytics buffer
client.flush_analytics()
```

## Documentation and Support

Read the [Flagpool Python SDK documentation](https://www.flagpool.io/docs/sdks/python) for integration guidance, or browse the [Flagpool documentation](https://www.flagpool.io/docs) for platform concepts and guides.

Report SDK bugs in [GitHub Issues](https://github.com/flagpool/flagpool-sdk/issues), or contact [Flagpool support](mailto:support@flagpool.io).

## License

MIT © [Flagpool](https://www.flagpool.io/en)
