Metadata-Version: 2.4
Name: flagkit
Version: 1.0.3
Summary: FlagKit SDK for Python - Feature flag evaluation and analytics
Project-URL: Homepage, https://flagkit.dev
Project-URL: Documentation, https://docs.flagkit.dev
Project-URL: Repository, https://github.com/teracrafts/flagkit-sdk
Project-URL: Issues, https://github.com/teracrafts/flagkit-sdk/issues
Author-email: FlagKit <sdk@flagkit.dev>
License-Expression: MIT
Keywords: feature-flags,feature-toggles,flagkit,sdk
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
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: Programming Language :: Python :: 3.13
Classifier: Typing :: Typed
Requires-Python: >=3.9
Requires-Dist: cryptography>=41.0.0
Requires-Dist: httpx>=0.25.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.1.0; extra == 'dev'
Requires-Dist: pytest-httpx>=0.28.0; extra == 'dev'
Requires-Dist: pytest>=7.4.0; extra == 'dev'
Requires-Dist: ruff>=0.1.9; extra == 'dev'
Description-Content-Type: text/markdown

# FlagKit Python SDK

Official Python SDK for [FlagKit](https://flagkit.dev) - Feature flag management made simple.

## Installation

```bash
pip install flagkit
```

## Requirements

- Python 3.9+
- `httpx` (automatically installed)
- `cryptography` (automatically installed, for cache encryption)

## Quick Start

```python
from flagkit import FlagKit

# Initialize the SDK
client = FlagKit.initialize(
    api_key="sdk_your_api_key",
    polling_interval=30,  # seconds
    cache_ttl=300,        # seconds
)

# Wait for initialization
client.wait_for_ready()

# Identify a user
client.identify("user-123", {"plan": "premium"})

# Evaluate flags
dark_mode = client.get_boolean_value("dark-mode", default=False)
welcome_message = client.get_string_value("welcome-message", default="Hello!")
max_items = client.get_number_value("max-items", default=10)
config = client.get_json_value("feature-config", default={})

# Get full evaluation details
result = client.evaluate("dark-mode")
print(f"Value: {result.value}, Reason: {result.reason}")

# Track custom events
client.track("button_clicked", {"button": "signup"})

# Cleanup when done
client.close()
```

## Features

- **Type-safe evaluation** - Boolean, string, number, and JSON flag types
- **Local caching** - Fast evaluations with configurable TTL and optional encryption
- **Background polling** - Automatic flag updates with jitter
- **Event tracking** - Analytics with batching and crash-resilient persistence
- **Resilient** - Circuit breaker, retry with exponential backoff, offline support
- **Thread-safe** - Safe for concurrent use

## API Reference

### Initialization

```python
from flagkit import FlagKit

# Using the static factory (recommended)
client = FlagKit.initialize(
    api_key="sdk_...",              # Required
    base_url="https://api.flagkit.dev/api/v1",  # Optional
    polling_interval=30.0,          # Seconds between polls
    enable_polling=True,            # Enable background polling
    cache_enabled=True,             # Enable local caching
    cache_ttl=300.0,                # Cache TTL in seconds
    offline=False,                  # Offline mode
    timeout=5.0,                    # Request timeout in seconds
    retries=3,                      # Number of retries
    bootstrap={"flag": True},       # Initial flag values
    debug=False,                    # Enable debug logging
    on_ready=lambda: print("Ready!"),
    on_error=lambda e: print(f"Error: {e}"),
    on_update=lambda flags: print(f"Updated: {len(flags)} flags"),
)
```

### Flag Evaluation

```python
# Boolean flags
enabled = client.get_boolean_value("feature-flag", default=False)

# String flags
variant = client.get_string_value("button-text", default="Click")

# Number flags
limit = client.get_number_value("rate-limit", default=100)

# JSON flags
config = client.get_json_value("config", default={"enabled": False})

# Full evaluation result
result = client.evaluate("feature-flag")
# result.flag_key, result.value, result.enabled, result.reason, result.version

# Evaluate all flags
all_results = client.evaluate_all()

# Check flag existence
if client.has_flag("my-flag"):
    # ...

# Get all flag keys
keys = client.get_all_flag_keys()
```

### Context Management

```python
from flagkit import EvaluationContext

# Set global context
context = EvaluationContext(
    user_id="user-123",
    email="user@example.com",
    country="US",
    custom={"plan": "premium", "beta_tester": True},
    private_attributes=["email"],  # Not sent to server
)
client.set_context(context)

# Get current context
current = client.get_context()

# Clear context
client.clear_context()

# Identify user (shorthand)
client.identify("user-123", {"email": "user@example.com"})

# Reset to anonymous
client.reset()

# Pass context to evaluation
result = client.get_boolean_value(
    "feature-flag",
    default=False,
    context=EvaluationContext(user_id="other-user"),
)
```

### Event Tracking

```python
# Track custom event
client.track("purchase", {
    "amount": 99.99,
    "currency": "USD",
    "product_id": "prod-123",
})

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

### Lifecycle

```python
# Check if SDK is ready
if client.is_ready():
    # ...

# Wait for ready (blocks until initialized)
client.wait_for_ready()

# Force refresh flags from server
client.refresh()

# Close SDK and cleanup
client.close()
```

## Error Handling

```python
from flagkit import FlagKitError, InitializationError, NetworkError

try:
    client = FlagKit.initialize(api_key="sdk_...")
except InitializationError as e:
    print(f"Failed to initialize: {e.code} - {e}")
except NetworkError as e:
    if e.recoverable:
        # Retry logic
        pass
except FlagKitError as e:
    print(f"Error [{e.code}]: {e}")
    print(f"Recoverable: {e.recoverable}")
    print(f"Details: {e.to_dict()}")
```

## Local Development

```python
# Connect to local FlagKit server at http://localhost:8200/api/v1
client = FlagKit.initialize(
    api_key="sdk_...",
    local_port=8200,
)

# Or use a custom port
client = FlagKit.initialize(
    api_key="sdk_...",
    local_port=3000,  # Uses http://localhost:3000/api/v1
)
```

## Offline Mode

```python
# Start in offline mode
client = FlagKit.initialize(
    api_key="sdk_...",
    offline=True,
    bootstrap={"feature-flag": True},
)

# Uses bootstrap values without network requests
value = client.get_boolean_value("feature-flag", default=False)
```

## PII Detection

The SDK can detect and warn about potential PII in contexts and events:

```python
# Enable strict PII mode - raises errors instead of warnings
client = FlagKit.initialize(
    api_key="sdk_...",
    strict_pii_mode=True,
)

# Use private attributes to mark fields as intentionally containing PII
context = EvaluationContext(
    user_id="user-123",
    email="user@example.com",
    private_attributes=["email"],  # Marks email as intentionally private
)
```

## Request Signing

POST requests to the FlagKit API are signed with HMAC-SHA256 for integrity. This is enabled by default and can be disabled via the `enable_request_signing` option.

## Thread Safety

All SDK methods are safe for concurrent use from multiple threads. The client uses internal synchronization (threading.Lock) to ensure thread-safe access to:

- Flag cache
- Event queue
- Context management
- Polling state

## License

MIT License - see [LICENSE](LICENSE) for details.
