Advanced Usage
This guide covers advanced patterns and best practices for using the Toggly Python SDK.
Feature Evaluation
Basic Evaluation
from toggly import TogglyClient, TogglyConfig
client = TogglyClient(TogglyConfig(app_key="your-app-key"))
client.init()
# Simple check
if client.is_enabled("my-feature"):
print("Feature enabled!")
# With default value
enabled = client.is_enabled("unknown-feature", default=True)
With Context
from toggly import EvaluationContext
context = EvaluationContext(
identity="user-123",
groups=["beta-testers", "premium"],
traits={
"plan": "enterprise",
"country": "US",
"signup_date": "2024-01-15",
}
)
if client.is_enabled("premium-feature", context):
print("Premium feature for this user!")
Feature Gates
Evaluate multiple features at once:
from toggly import FeatureRequirement
# All features must be enabled
if client.evaluate_gate(
["feature-a", "feature-b"],
FeatureRequirement.ALL,
context
):
print("All features enabled!")
# Any feature must be enabled
if client.evaluate_gate(
["premium", "trial"],
FeatureRequirement.ANY,
context
):
print("User has access!")
# Negate the result
if client.evaluate_gate(
["maintenance-mode"],
FeatureRequirement.ALL,
context,
negate=True
):
print("Not in maintenance mode!")
Get All Flags
# Get all feature flags as a dictionary
flags = client.feature_flags
for key, enabled in flags.items():
print(f"{key}: {'enabled' if enabled else 'disabled'}")
Async Support
AsyncTogglyClient
import asyncio
from toggly import AsyncTogglyClient, TogglyConfig
async def main():
config = TogglyConfig(app_key="your-app-key")
client = AsyncTogglyClient(config)
await client.init()
if await client.is_enabled("async-feature"):
print("Feature enabled!")
await client.close()
asyncio.run(main())
Context Manager
async with AsyncTogglyClient(config) as client:
await client.init()
enabled = await client.is_enabled("my-feature")
With FastAPI
For native middleware and gate dependencies, use the synchronous client in the FastAPI lifespan example. For direct async evaluation, own the async client's lifespan and await every check:
import os
from contextlib import asynccontextmanager
from fastapi import FastAPI, Request
from toggly import AsyncTogglyClient, TogglyConfig
@asynccontextmanager
async def lifespan(app: FastAPI):
client = AsyncTogglyClient(TogglyConfig(
app_key=os.environ.get("TOGGLY_APP_KEY"),
))
await client.init()
app.state.feature_client = client
try:
yield
finally:
await client.close()
app = FastAPI(lifespan=lifespan)
@app.get("/feature")
async def feature(request: Request):
enabled = await request.app.state.feature_client.is_enabled("async-feature")
return {"enabled": enabled}
Testing
Mock the Client
- unittest
- pytest
from unittest.mock import MagicMock, patch
def test_feature_enabled():
mock_client = MagicMock()
mock_client.is_enabled.return_value = True
with patch("your_module.get_toggly_client", return_value=mock_client):
result = your_function()
assert result == "enabled"
mock_client.is_enabled.assert_called_with("my-feature")
import pytest
from unittest.mock import MagicMock
@pytest.fixture
def mock_toggly(mocker):
mock_client = MagicMock()
mocker.patch("your_module.get_toggly_client", return_value=mock_client)
return mock_client
def test_feature_enabled(mock_toggly):
mock_toggly.is_enabled.return_value = True
result = your_function()
assert result == "enabled"
Feature Defaults for Testing
# Use feature_defaults for predictable test behavior
config = TogglyConfig(
app_key="test-key",
feature_defaults={
"feature-a": True,
"feature-b": False,
},
disable_background_refresh=True, # Disable network calls in tests
)
client = TogglyClient(config)
# No need to call init() - defaults will be used
Test Fixtures
import pytest
from toggly import TogglyClient, TogglyConfig, set_default_client
@pytest.fixture
def toggly_client():
config = TogglyConfig(
app_key="test-key",
feature_defaults={"test-feature": True},
disable_background_refresh=True,
)
client = TogglyClient(config)
set_default_client(client)
yield client
client.close()
def test_with_toggly(toggly_client):
assert toggly_client.is_enabled("test-feature") is True
Error Handling
Graceful Degradation
from toggly import TogglyClient, TogglyConfig, TogglyError
try:
client = TogglyClient(config)
client.init()
except TogglyError as e:
print(f"Failed to initialize Toggly: {e}")
# Use defaults or disable feature flags
client = None
def is_feature_enabled(feature_key: str, default: bool = False) -> bool:
if client is None:
return default
try:
return client.is_enabled(feature_key, default=default)
except TogglyError:
return default
Timeout Handling
config = TogglyConfig(
app_key="your-app-key",
connect_timeout=5.0, # Connection timeout
request_timeout=10.0, # Request timeout
)
Retry Logic
The SDK includes built-in retry logic for transient failures. For custom retry behavior:
import time
from toggly import TogglyClient, TogglyConfig, TogglyError
def init_with_retry(config: TogglyConfig, max_retries: int = 3):
client = TogglyClient(config)
for attempt in range(max_retries):
try:
client.init()
return client
except TogglyError as e:
if attempt == max_retries - 1:
raise
time.sleep(2 ** attempt) # Exponential backoff
return client
Background Refresh
Automatic Refresh
config = TogglyConfig(
app_key="your-app-key",
refresh_interval=60.0, # Refresh every 60 seconds
)
Manual Refresh
# Force refresh from API
client.refresh()
Disable Background Refresh
config = TogglyConfig(
app_key="your-app-key",
disable_background_refresh=True,
)
# Manually refresh when needed
client.refresh()
Usage Tracking
Enable/Disable Tracking
config = TogglyConfig(
app_key="your-app-key",
enable_usage_tracking=True, # Default
)
Flush Metrics
# Manually flush usage metrics
client.flush_metrics()
Signed Definitions
For enhanced security, use signed feature definitions:
config = TogglyConfig(
app_key="your-app-key",
use_signed_definitions=True,
)
Logging
Enable Debug Logging
import logging
# Configure logging
logging.basicConfig(level=logging.DEBUG)
# Or for just Toggly
logger = logging.getLogger("toggly")
logger.setLevel(logging.DEBUG)
config = TogglyConfig(
app_key="your-app-key",
debug=True,
)
Custom Logger
import logging
custom_logger = logging.getLogger("my-app.toggly")
custom_logger.setLevel(logging.INFO)
# The SDK respects the logging configuration
Multi-Environment
Environment-Specific Config
import os
environment = os.getenv("ENV", "Production")
config = TogglyConfig(
app_key=os.getenv("TOGGLY_APP_KEY"),
environment=environment,
)
Multiple Clients
# Production client
prod_config = TogglyConfig(
app_key="prod-key",
environment="Production",
)
prod_client = TogglyClient(prod_config)
prod_client.init()
# Staging client
staging_config = TogglyConfig(
app_key="staging-key",
environment="Staging",
)
staging_client = TogglyClient(staging_config)
staging_client.init()
Lifecycle Management
Application Startup/Shutdown
from toggly import TogglyClient, TogglyConfig, set_default_client
# Global client
_client: TogglyClient | None = None
def init_toggly():
global _client
config = TogglyConfig(app_key="your-app-key")
_client = TogglyClient(config)
_client.init()
set_default_client(_client)
def shutdown_toggly():
global _client
if _client:
_client.close()
_client = None
With atexit
import atexit
from toggly import TogglyClient, TogglyConfig
config = TogglyConfig(app_key="your-app-key")
client = TogglyClient(config)
client.init()
# Register cleanup
atexit.register(client.close)
Performance Optimization
Caching
The core client evaluates definitions locally. Add a file snapshot for recovery across restarts:
from toggly import TogglyClient, TogglyConfig
from toggly.providers import FileSnapshotProvider
config = TogglyConfig(
app_key="your-app-key",
snapshot_provider=FileSnapshotProvider(directory="./cache/my-app-production"),
)
client = TogglyClient(config)
client.init() # Close once at application shutdown.
See Caching for the core snapshot interface and the separate Redis/Memcached definition-list utilities.
Batch Evaluation
Evaluate multiple features efficiently:
# Instead of multiple calls
features = ["feature-a", "feature-b", "feature-c"]
results = {f: client.is_enabled(f, context) for f in features}
Local Evaluation
Feature definitions are cached locally after the initial fetch:
# First call fetches from API (or cache)
client.init()
# Subsequent calls use local cache
client.is_enabled("feature") # Fast, no network call
Type Safety
Type Hints
The SDK is fully typed for IDE support:
from toggly import (
TogglyClient,
TogglyConfig,
EvaluationContext,
FeatureRequirement,
)
def check_feature(
client: TogglyClient,
feature: str,
context: EvaluationContext | None = None,
) -> bool:
return client.is_enabled(feature, context)
Protocol Support
from typing import Protocol
class FeatureFlagClient(Protocol):
def is_enabled(
self,
feature_key: str,
context: EvaluationContext | None = None,
default: bool = False,
) -> bool: ...
def use_client(client: FeatureFlagClient):
if client.is_enabled("my-feature"):
print("Enabled!")