Skip to navigation

Python SDK

View as Markdown

The Unleash Python SDK lets you evaluate feature flags in Python applications. It connects to Unleash or Unleash Edge to fetch flag configurations and evaluates them locally against an Unleash context.

You can use this SDK with Unleash Enterprise or Unleash Open Source.

For an overview of how Unleash SDKs work, including offline behavior, feature compatibility across SDKs, and default refresh and metrics intervals, refer to the SDK overview.

The SDK includes two clients: UnleashClient for synchronous applications and AsyncUnleashClient, an asyncio-native client for applications built on frameworks such as FastAPI, Starlette, or aiohttp. Both expose the same API for evaluating flags; they differ in how you initialize and stop them.

Requirements

  • Python 3.8 or later

Installation

Install the SDK from PyPI:

pip install UnleashClient

Initialization

Create a single Unleash client instance and share it across your application. Running multiple instances can lead to significantly increased request volume and may count as additional SDK connections toward your usage limits.

Set url to the /api endpoint of your Unleash instance or your Unleash Edge instance. Set the API token to a backend API token.

Flag checks are regular synchronous methods on both clients, because evaluation happens locally against the SDK’s cached configuration and performs no I/O. Only the async client’s lifecycle methods are coroutines.

from UnleashClient import UnleashClient
client = UnleashClient(
url="https://<your-unleash-instance>/api",
app_name="my-python-app",
custom_headers={"Authorization": "<your-backend-token>"},
)
client.initialize_client()

You can also use the client as a context manager, which initializes the client on entry and shuts it down on exit:

with UnleashClient(
url="https://<your-unleash-instance>/api",
app_name="my-python-app",
custom_headers={"Authorization": "<your-backend-token>"},
) as client:
client.is_enabled("my-flag")

Check if the SDK is ready

Once initialized but not yet synchronized with Unleash, all flags evaluate to false and variants resolve to the disabled variant, unless you have a bootstrapped configuration or use a fallback function.

Initialize the client before calling is_enabled or get_variant. Flag evaluation is only supported after initialization.

To know when flag configuration is available, subscribe to the READY event. The SDK emits it when it first loads flags from the Unleash server or the local cache. The is_initialized property only tells you that initialize_client() has completed, which can happen before any flags are loaded.

The event callback runs on a background thread:

UnleashClient makes its first fetch during initialize_client(), before the call returns. If that fetch fails, the client keeps polling and emits READY once a fetch succeeds.

import threading
from UnleashClient import UnleashClient
from UnleashClient.events import BaseEvent, UnleashEventType
ready = threading.Event()
def on_event(event: BaseEvent) -> None:
if event.event_type == UnleashEventType.READY:
ready.set()
client = UnleashClient(
url="https://<your-unleash-instance>/api",
app_name="my-python-app",
custom_headers={"Authorization": "<your-backend-token>"},
event_callback=on_event,
)
client.initialize_client()
if ready.wait(timeout=5):
enabled = client.is_enabled("my-flag")

Configuration options

Both client constructors support the following parameters. Parameters marked “Sync client only” apply only to UnleashClient.

url
strRequired

URL of your Unleash or Unleash Edge API, for example https://<your-unleash-instance>/api.

app_name
strRequired

Name of the application using the client. Included in registration, metrics, and request headers.

environment
strDefaults to defaultDeprecated

Logical environment name. Deprecated; the environment is determined by your API token.

instance_id
strDefaults to unleash-python-sdk

Unique identifier for this client instance.

refresh_interval
intDefaults to 15

How often, in seconds, the SDK polls for updated flag configuration.

refresh_jitter
intDefaults to None

Maximum jitter, in seconds, added to the refresh interval.

metrics_interval
intDefaults to 60

How often, in seconds, the SDK sends usage metrics to Unleash.

metrics_jitter
intDefaults to None

Maximum jitter, in seconds, added to the metrics interval.

disable_metrics
boolDefaults to False

Disables sending usage metrics to Unleash.

disable_registration
boolDefaults to False

Disables client registration with Unleash.

custom_headers
dictDefaults to None

Additional headers for all API requests. Use this to add Authorization.

custom_options
dictDefaults to None

Extra options passed to the underlying HTTP requests, for example to skip SSL verification. Sync client only.

request_timeout
intDefaults to 30

Timeout, in seconds, for requests to the Unleash server.

request_retries
intDefaults to 3

Number of retries for requests to the Unleash server.

custom_strategies
dictDefaults to None

Dictionary of strategy names to strategy objects. See custom strategies.

cache_directory
strDefaults to None

Location of the on-disk cache. When unset, the cache library determines the location.

project_name
strDefaults to NoneDeprecated

Restricts flag fetches to a single project. Deprecated; use a project-scoped API token instead.

verbose_log_level
intDefaults to 30

Numerical log level used when checking a feature flag fails.

cache
BaseCacheDefaults to None

Custom cache implementation that extends UnleashClient.cache.BaseCache. See custom cache. When unset, the SDK uses an on-disk cache.

scheduler
BaseSchedulerDefaults to None

Custom APScheduler instance. Use this to customize job stores or executors. When unset, the SDK creates its own scheduler. Sync client only.

scheduler_executor
strDefaults to None

Name of the APScheduler executor to use with a custom scheduler. Sync client only.

multiple_instance_mode
InstanceAllowTypeDefaults to WARN

How the SDK handles multiple client instances: BLOCK fails construction, WARN logs a warning, and SILENTLY_ALLOW does nothing.

event_callback
CallableDefaults to None

Function that receives SDK events, including impression events. Called on a dedicated background thread. If you run under a WSGI server or in multiple processes, see framework integration.

sdk_flavor
strDefaults to None

Identifier of an integration built on top of this SDK, such as an OpenFeature provider. Leave unset for plain SDK usage.

sdk_flavor_version
strDefaults to None

Version of the integration named by sdk_flavor.

Check flags

Use is_enabled to evaluate a flag. Evaluation happens locally against the SDK’s cached configuration.

if client.is_enabled("my-flag"):
# new behavior
...

Check variants

Use get_variant to evaluate a flag with variants. The method returns a dictionary:

variant = client.get_variant("my-flag")
# {
# "name": "variant1",
# "payload": {"type": "string", "value": "val1"},
# "enabled": True,
# "feature_enabled": True,
# }

The SDK can return two forms of the disabled variant. In both cases, name is disabled and enabled is False:

{
"name": "disabled",
"enabled": False,
"feature_enabled": False,
}

Use feature_enabled when you need the flag’s state alongside the variant result, without a separate is_enabled call.

The enabled: False field marks the result as the SDK’s default disabled variant. This distinguishes it from a real variant that happens to be named disabled, which the SDK returns with enabled: True.

Fallback function

You can specify a fallback function for cases where the client doesn’t recognize the flag by using the fallback_function keyword argument:

def custom_fallback(feature_name: str, context: dict) -> bool:
return True
client.is_enabled("my-flag", fallback_function=custom_fallback)

The fallback function must accept the feature name and context as positional arguments, in that order.

The client evaluates the fallback function if the feature flag is not found or if an exception occurs during evaluation. If the fallback function itself raises an exception, is_enabled returns False and logs a warning.

The fallback function runs as part of flag evaluation, so keep it fast: its runtime adds directly to every is_enabled call that invokes it.

Unleash context

The Unleash context holds properties such as user ID, session ID, and custom fields that drive flag evaluation, including strategy targeting, constraints, and stickiness.

Both is_enabled and get_variant accept a context dictionary as the second parameter:

app_context = {
"userId": "user-123",
"sessionId": "session-abc",
"properties": {
"custom-property": "some-value"
}
}
client.is_enabled("my-flag", app_context)
client.get_variant("my-flag", app_context)

The SDK enriches the context automatically before evaluation:

  • appName and environment are set from the client configuration.
  • currentTime is set to the current UTC time when you don’t provide it, so date-based constraints work without extra setup.
  • Any top-level key that isn’t a standard context field is moved into properties. Passing {"custom-property": "some-value"} at the top level is equivalent to nesting it under properties.

Context values can be any type that has a __str__ implementation. Explicitly supported types are:

  • Numerics
  • Strings
  • Dates
  • UUIDs

Gradual rollout strategies require you to pass either a userId or a sessionId for stickiness to work correctly.

Stop the client

Call destroy() to shut down the SDK and flush any pending metrics to Unleash. If you use the client as a context manager, this happens automatically on exit.

client.destroy()

A destroyed client cannot be reinitialized. Create a new instance if you need to reconnect.

Custom strategies

The SDK supports all built-in activation strategies. You can also define custom activation strategies if the built-in ones don’t cover your needs.

A custom strategy is a class that implements an apply method:

class ActiveForEmailStrategy:
def apply(self, parameters: dict, context: dict = None) -> bool:
# Decide if the feature is active for this context
return context.get("email") in parameters

Once you’ve defined your strategy, register it when you initialize the client. The key must exactly match the strategy name in Unleash:

# Requires a custom strategy defined in Unleash called 'EmailStrategy'
my_custom_strategies = {
"EmailStrategy": ActiveForEmailStrategy()
}
client = UnleashClient(
url="https://<your-unleash-instance>/api",
app_name="my-python-app",
custom_headers={"Authorization": "<your-backend-token>"},
custom_strategies=my_custom_strategies,
)

Events

To subscribe to SDK events, provide an event_callback function when you initialize the client. The callback receives a single event object.

Event typeDescription
READYThe SDK loaded feature flags from the Unleash server or the local cache. Emitted at least once.
FETCHEDA new version of the feature flags was fetched from the server. The event includes a features property with all flags returned by that fetch.
FEATURE_FLAGA flag with impression data enabled was evaluated with is_enabled.
VARIANTA flag with impression data enabled was evaluated with get_variant.

The SDK invokes your callback on a dedicated background thread. Flag evaluation does not wait for the callback, so a slow callback cannot block your application. Two consequences of this design:

  • The callback cannot read thread-local state from the calling thread, such as Flask’s g object, the current Django request, or contextvars.
  • If the callback cannot keep up, the SDK drops events and logs a warning instead of blocking.

Set event_callback when you construct the client; reassigning it afterwards has no effect.

Both clients deliver events this way. With AsyncUnleashClient, the callback is still a regular function, not a coroutine, and it runs on the background thread, outside the event loop.

The SDK does not include a built-in event bus. The example below uses Blinker to send signals.

from blinker import signal
from UnleashClient import UnleashClient
from UnleashClient.events import UnleashEvent, UnleashEventType
send_data = signal('send-data')
@send_data.connect
def receive_data(sender, **kw):
if kw["data"].event_type == UnleashEventType.READY:
print("SDK is ready: flags loaded from Unleash or local cache")
elif kw["data"].event_type == UnleashEventType.FETCHED:
print("Fetched new feature flags:", kw["data"].features)
def example_callback(event: UnleashEvent):
send_data.send('anonymous', data=event)
client = UnleashClient(
url="https://<your-unleash-instance>/api",
app_name="my-python-app",
custom_headers={"Authorization": "<your-backend-token>"},
event_callback=example_callback,
)
client.initialize_client()
client.is_enabled("my-flag")

Impression data

When impression data is enabled for a flag, the SDK emits an impression event each time that flag is evaluated. Use impression events to track feature exposures in analytics tools.

Impression events arrive through the same event_callback with the event types FEATURE_FLAG and VARIANT. Each event includes the feature_name, the enabled result, and, for variant checks, the variant name:

from UnleashClient.events import UnleashEvent, UnleashEventType
def example_callback(event: UnleashEvent):
if event.event_type == UnleashEventType.FEATURE_FLAG:
print(f"{event.feature_name} evaluated to {event.enabled}")
elif event.event_type == UnleashEventType.VARIANT:
print(f"{event.feature_name} resolved to variant {event.variant}")

Impact metrics

Impact metrics are lightweight, application-level time-series metrics stored and visualized directly inside Unleash. Use them to connect application data, such as request counts, error rates, or latency, to your feature flags and release plans.

The SDK automatically attaches appName and environment labels to all impact metrics.

Define and record metrics through the client’s impact_metrics property:

from UnleashClient import UnleashClient
client = UnleashClient(
url="https://<your-unleash-instance>/api",
app_name="my-python-app",
custom_headers={"Authorization": "<your-backend-token>"},
)
client.initialize_client()
client.impact_metrics.define_counter("request_count", "Total number of HTTP requests")
client.impact_metrics.increment_counter("request_count")
client.impact_metrics.define_gauge("active_users", "Current number of active users")
client.impact_metrics.update_gauge("active_users", 42)
client.impact_metrics.define_histogram(
"request_time_ms",
"Request processing time in milliseconds",
buckets=[50, 100, 200, 500, 1000],
)
client.impact_metrics.observe_histogram("request_time_ms", 125)

Metric types

Use counters for cumulative values that only increase, such as the total number of requests or errors.

client.impact_metrics.define_counter("request_count", "Total number of HTTP requests")
client.impact_metrics.increment_counter("request_count")

Impact metrics are batched and sent on the same interval as standard SDK metrics, controlled by metrics_interval.

Bootstrap

By default, the SDK fetches your feature flags from the Unleash API at startup. To make your application more resilient, for example during network outages, you can bootstrap the client with a local or remote flag configuration:

  1. Create a FileCache, or your own BaseCache implementation.
  2. Pre-seed it with feature flags using bootstrap_from_dict, bootstrap_from_file, or bootstrap_from_url.
  3. Pass the cache to the client constructor.

A bootstrapped client can evaluate flags before its first fetch from Unleash completes. The two clients load the bootstrapped configuration at different points:

  • UnleashClient loads it when you construct the client.
  • AsyncUnleashClient loads it when you call initialize_client().

The examples below use the sync client. Pass the cache to AsyncUnleashClient the same way.

from UnleashClient.cache import FileCache
from UnleashClient import UnleashClient
cache = FileCache("my-cache")
cache.bootstrap_from_dict({
"version": 2,
"features": [
{
"name": "my-flag",
"enabled": True,
"strategies": [{"name": "default"}],
}
]
})
client = UnleashClient(
url="https://<your-unleash-instance>/api",
app_name="my-python-app",
custom_headers={"Authorization": "<your-backend-token>"},
cache=cache,
)

Local caching and offline behavior

The SDK persists the latest flag configuration to an on-disk cache between restarts, using fcache. Set cache_directory to control where the cache is stored.

On startup, the SDK loads cached state first, then fetches fresh configuration from Unleash. If Unleash is temporarily unreachable, the SDK continues serving cached or bootstrapped values and keeps polling on refresh_interval.

Custom cache

If you need a different storage backend, for example Redis, memory-only, or a custom database, provide your own cache implementation that extends BaseCache.

Below is an example custom cache using fcache under the hood:

from typing import Optional, Any
from UnleashClient.cache import BaseCache
from fcache.cache import FileCache as _FileCache
class CustomCache(BaseCache):
# This is specific to FileCache. Depending on the cache you're using, this may look different.
def __init__(self, name: str, directory: Optional[str] = None):
self._cache = _FileCache(name, app_cache_dir=directory)
def set(self, key: str, value: Any):
self._cache[key] = value
self._cache.sync()
def mset(self, data: dict):
self._cache.update(data)
self._cache.sync()
def get(self, key: str, default: Optional[Any] = None):
return self._cache.get(key, default)
def exists(self, key: str):
return key in self._cache
def destroy(self):
return self._cache.delete()

Pass your cache instance to either client with the cache argument:

client = UnleashClient(
url="https://<your-unleash-instance>/api",
app_name="my-python-app",
custom_headers={"Authorization": "<your-backend-token>"},
cache=CustomCache("my-cache"),
)

Framework integration

UnleashClient runs background threads to poll for feature flags and send metrics, and both clients deliver events on a background thread. Some runtime environments, like WSGI servers and Celery workers, restrict threads or spawn new processes, and need extra setup to make sure the SDK works correctly.

WSGI

When using WSGI servers, for example with Flask or Django apps, be aware that:

  • Many WSGI setups disable threading by default, so set enable-threads in your WSGI config. Without it, UnleashClient cannot poll for updates in the background, and neither client can deliver events to your callback.
  • When running under uWSGI with multiple processes using the --processes option, you may need to enable the lazy-apps option. This ensures each process gets a fresh SDK instance.

See The Art of Graceful Reloading for more details.

Celery

When using the SDK in Celery tasks, initialize it inside the worker_process_init event. Otherwise, the worker may run but won’t poll for feature flag updates or receive event updates.

from UnleashClient import UnleashClient
from celery.signals import worker_process_init
client = UnleashClient(
url="https://<your-unleash-instance>/api",
app_name="my-python-app",
custom_headers={"Authorization": "<your-backend-token>"},
)
@worker_process_init.connect
def configure_workers(sender=None, conf=None, **kwargs):
client.initialize_client()

Multi-process setups

When each new process creates its own client, every process runs an independent poll cycle. With many short-lived processes, this wastes system resources and counts against your Unleash usage.

To avoid this, set up a leader and follower pattern: one process keeps the on-disk cache fresh, and the other processes call initialize_client(fetch_toggles=False). These clients then read flag configuration from the cache instead of polling the Unleash API.

This mode is available with UnleashClient. AsyncUnleashClient accepts the fetch_toggles argument for compatibility and always polls.

Unit testing

To test code that depends on feature flags without a running Unleash instance, bootstrap a cache with the flag state you want and turn off all network activity:

from UnleashClient import UnleashClient
from UnleashClient.cache import FileCache
def make_test_client(cache_dir: str) -> UnleashClient:
cache = FileCache("test-cache", directory=cache_dir)
cache.bootstrap_from_dict({
"version": 2,
"features": [
{
"name": "my-flag",
"enabled": True,
"strategies": [{"name": "default"}],
}
]
})
client = UnleashClient(
url="http://localhost:4242/api",
app_name="test-app",
disable_metrics=True,
disable_registration=True,
cache=cache,
)
client.initialize_client(fetch_toggles=False)
return client
def test_my_feature(tmp_path):
client = make_test_client(str(tmp_path))
assert client.is_enabled("my-flag")
client.destroy()

With fetch_toggles=False, the client loads flags from the cache only, so tests are deterministic and make no network requests. This approach uses UnleashClient, because fetch_toggles=False is specific to the sync client.

Migrating to v6

If you use custom strategies or access the features property on the Unleash client, read the complete v6 migration guide before upgrading.

Troubleshooting

If is_enabled always returns False:

  • Confirm the flag exists and is enabled in the target environment.
  • Verify url points to your Unleash API base URL, for example https://<your-unleash-instance>/api.
  • Verify custom_headers includes a valid backend API token in Authorization.
  • Verify the SDK completed its initial sync by checking that the READY event fired. is_initialized being True only means that initialize_client() has completed.
  • Verify your Unleash context fields match your rollout strategy and constraints.

If the SDK initializes but flag changes never arrive:

  • Under uWSGI, enable the enable-threads option; the SDK needs threads to poll in the background. With multiple processes, also consider lazy-apps.
  • In Celery workers, initialize the client inside the worker_process_init signal.

See Framework integration for details.