Metadata-Version: 2.5
Name: featureflip
Version: 2.10.0
Summary: Python SDK for Featureflip - a feature flag SaaS platform
Project-URL: Homepage, https://featureflip.io
Project-URL: Documentation, https://featureflip.io/docs/sdks/python/
Project-URL: Repository, https://github.com/canopy-labs/featureflip-python
Author: Featureflip Team
License-Expression: Apache-2.0
License-File: LICENSE
Keywords: feature-flags,feature-toggles,sdk
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Typing :: Typed
Requires-Python: >=3.10
Requires-Dist: httpx-sse>=0.4.0
Requires-Dist: httpx>=0.27.0
Requires-Dist: structlog>=24.0.0
Provides-Extra: dev
Requires-Dist: mypy>=1.8.0; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.23.0; extra == 'dev'
Requires-Dist: pytest-cov>=4.0.0; extra == 'dev'
Requires-Dist: pytest>=8.0.0; extra == 'dev'
Requires-Dist: respx>=0.21.0; extra == 'dev'
Requires-Dist: ruff>=0.3.0; extra == 'dev'
Description-Content-Type: text/markdown

# Featureflip Python SDK

Python SDK for [Featureflip](https://featureflip.io) - evaluate feature flags locally with near-zero latency.

## Installation

```bash
pip install featureflip
```

## Quick Start

```python
from featureflip import FeatureflipClient

# Initialize the client (blocks until flags are loaded)
client = FeatureflipClient(sdk_key="your-sdk-key")

# Evaluate a feature flag
enabled = client.variation("my-feature", {"user_id": "user-123"}, default=False)

if enabled:
    print("Feature is enabled!")
else:
    print("Feature is disabled")

# Clean shutdown
client.close()
```

> **Lifetime:** Multiple `FeatureflipClient(sdk_key="x")` calls with the same SDK key return distinct handles sharing one underlying client — you cannot accidentally open duplicate streaming connections by constructing multiple clients. `close()` is refcounted; the real shutdown runs only when the last handle is closed.

## Configuration

```python
from featureflip import FeatureflipClient, Config

client = FeatureflipClient(
    sdk_key="your-sdk-key",
    config=Config(
        base_url="https://eval.featureflip.io",  # Evaluation API URL
        streaming=True,           # Use SSE for real-time updates (default)
        poll_interval=30.0,       # Polling interval if streaming=False
        send_events=True,         # Enable analytics event tracking
        flush_interval=30.0,      # Event flush interval in seconds
        init_timeout=10.0,        # Max seconds to wait for initialization
    )
)
```

The SDK key can also be set via the `FEATUREFLIP_SDK_KEY` environment variable.

## Context Manager

```python
with FeatureflipClient(sdk_key="your-sdk-key") as client:
    enabled = client.variation("my-feature", {"user_id": "123"}, default=False)
# Automatically closes and flushes events on exit
```

## Evaluation

```python
# Boolean flag
enabled = client.variation("feature-key", {"user_id": "123"}, default=False)

# String flag
tier = client.variation("pricing-tier", {"user_id": "123"}, default="free")

# Number flag
limit = client.variation("rate-limit", {"user_id": "123"}, default=100)

# JSON flag
config = client.variation("ui-config", {"user_id": "123"}, default={"theme": "light"})
```

### Detailed Evaluation

```python
detail = client.variation_detail("feature-key", {"user_id": "123"}, default=False)

print(detail.value)    # The evaluated value
print(detail.reason)   # "RULE_MATCH", "FALLTHROUGH", "FLAG_DISABLED", etc.
print(detail.rule_id)  # Rule ID if reason is RULE_MATCH
```

## Event Tracking

```python
# Track custom events
client.track("checkout-completed", {"user_id": "123"}, metadata={"total": 99.99})

# Record an identify event for analytics (does not affect flag evaluation)
client.identify({"user_id": "123", "email": "user@example.com", "plan": "pro"})

# Force flush pending events
client.flush()
```

Featureflip counts each event name, per environment. The metadata you pass is transmitted with the event but is not stored, and event counts are not surfaced in the app or API.

## Flag Change Notifications

Subscribe to flag-configuration changes. The listener receives the keys affected
by each change, batched into one call:

```python
def on_flags_changed(keys):
    print("flags changed:", keys)

unsubscribe = client.on_update(on_flags_changed)
...
unsubscribe()   # or client.off_update(on_flags_changed)
```

The initial flag load does not fire. Beyond flags whose own configuration
changed, an update also names flags whose evaluated value moves as a result:
those referencing an edited segment, and those depending on a changed flag
through a prerequisite.

Listeners run on the SDK's streaming or polling thread, so they must not block.
A listener that raises is logged and isolated, affecting neither flag delivery
nor the other listeners.

## Testing

Use the test client for deterministic unit tests:

```python
from featureflip import FeatureflipClient

# Create a test client with fixed values (no network calls)
client = FeatureflipClient.for_testing({
    "my-feature": True,
    "pricing-tier": "pro",
})

# Evaluations return the configured values
assert client.variation("my-feature", {}, default=False) is True
assert client.variation("pricing-tier", {}, default="free") == "pro"

# Unknown flags return the default
assert client.variation("unknown", {}, default="fallback") == "fallback"

# Update values at runtime
client.set_test_value("my-feature", False)
```

## Features

- **Client-side evaluation** - Near-zero latency after initialization
- **Real-time updates** - SSE streaming with polling fallback
- **Change notifications** - `on_update()` reports exactly which flags moved
- **Event tracking** - Automatic batching and flushing of analytics events
- **Test support** - `for_testing()` factory for deterministic unit tests
- **Type-safe** - Full type hints with mypy strict mode compliance

## Requirements

- Python 3.10+

## Development

```bash
# Install development dependencies
pip install -e ".[dev]"

# Run tests
pytest

# Run linting
ruff check src/featureflip tests

# Run type checking
mypy src/featureflip --strict
```

## License

Apache-2.0
