Metadata-Version: 2.4
Name: flagpool-sdk
Version: 0.3.2
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"

# Flagpool SDK for Python

Official Python SDK for [Flagpool](https://flagpool.io) - the modern feature flag platform for teams who want control without complexity.

## Installation

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

## Quick Start

```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()
```

## Features

- ✅ **Local evaluation** - No server roundtrip per flag check
- ✅ **Deterministic rollouts** - Same user always gets same variation
- ✅ **Multiple flag types** - Boolean, string, number, JSON (dict)
- ✅ **Advanced targeting** - 8 operators including target lists
- ✅ **Real-time updates** - Automatic polling for flag changes
- ✅ **Offline support** - Works with cached flags when offline
- ✅ **Minimal dependencies** - Only requires `requests`

## 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

For complete documentation, guides, and best practices, visit:

📚 **[flagpool.io/docs](https://flagpool.io/docs)**

## Support

- 📖 [Documentation](https://flagpool.io/docs)
- 🐛 [Report Issues](https://github.com/flagpool/flagpool-sdk/issues)
- ✉️ [Email Support](mailto:support@flagpool.io)

## License

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

