> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.getunleash.io/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.getunleash.io/_mcp/server.

# Python SDK

> Integrate feature flags in your Python applications using the Unleash Python SDK.

The Unleash Python SDK lets you evaluate feature flags in Python applications. It connects to Unleash or [Unleash Edge](/unleash-edge) to fetch flag configurations and evaluates them locally against an [Unleash context](/concepts/unleash-context).

You can use this SDK with [Unleash Enterprise](https://www.getunleash.io/pricing) or [Unleash Open Source](https://github.com/Unleash/unleash).

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](/sdks).

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:

#### Sync

```bash
pip install UnleashClient
```

#### Async

SDK v6.9.0+

```bash
pip install "UnleashClient[async]"
```

The `async` extra installs the SDK together with [aiohttp](https://docs.aiohttp.org/), which `AsyncUnleashClient` uses to communicate with Unleash. Importing `AsyncUnleashClient` without aiohttp installed raises an `ImportError`.

## 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](/unleash-edge) instance. Set the API token to a [backend API token](/concepts/api-tokens-and-client-keys#backend-tokens).

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.

#### Sync

```python
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:

```python
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")
```

#### Async

SDK v6.9.0+

```python
from UnleashClient import AsyncUnleashClient

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

await client.initialize_client()
```

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

```python
async with AsyncUnleashClient(
    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](/concepts/feature-flag-variants#the-disabled-variant), unless you have a [bootstrapped configuration](#bootstrap) or use a [fallback function](#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](#events). 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:

#### Sync

`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.

```python
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")
```

#### Async

SDK v6.9.0+

Because the callback runs on a thread, bridge it to the event loop with a thread-safe primitive such as the `Event` from [aiologic](https://pypi.org/project/aiologic/):

```python
import asyncio

from aiologic import Event

from UnleashClient import AsyncUnleashClient
from UnleashClient.events import BaseEvent, UnleashEventType


async def main() -> None:
    ready = Event()

    def on_event(event: BaseEvent) -> None:
        if event.event_type == UnleashEventType.READY:
            ready.set()

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

    await client.initialize_client()

    if not await ready.with_(timeout=30):
        raise TimeoutError("Unleash did not signal READY within 30 seconds")

    enabled = client.is_enabled("my-flag")


if __name__ == "__main__":
    asyncio.run(main())
```

## Configuration options

Both client constructors support the following parameters. Parameters marked "Sync client only" apply only to `UnleashClient`.

**`url`** `str` — required

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

---

**`app_name`** `str` — required

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

---

**`environment`** `str` — deprecated, default: default

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

---

**`instance_id`** `str` — default: unleash-python-sdk

Unique identifier for this client instance.

---

**`refresh_interval`** `int` — default: 15

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

---

**`refresh_jitter`** `int` — default: None

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

---

**`metrics_interval`** `int` — default: 60

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

---

**`metrics_jitter`** `int` — default: None

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

---

**`disable_metrics`** `bool` — default: False

Disables sending usage metrics to Unleash.

---

**`disable_registration`** `bool` — default: False

Disables client registration with Unleash.

---

**`custom_headers`** `dict` — default: None

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

---

**`custom_options`** `dict` — default: None

Extra options passed to the underlying [HTTP requests](https://requests.readthedocs.io/en/latest/api/#main-interface), for example to skip SSL verification. Sync client only.

---

**`request_timeout`** `int` — default: 30

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

---

**`request_retries`** `int` — default: 3

Number of retries for requests to the Unleash server.

---

**`custom_strategies`** `dict` — default: None

Dictionary of strategy names to strategy objects. See [custom strategies](#custom-strategies).

---

**`cache_directory`** `str` — default: None

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

---

**`project_name`** `str` — deprecated, default: None

Restricts flag fetches to a single project. Deprecated; use a [project-scoped API token](/concepts/api-tokens-and-client-keys#api-token-format) instead.

---

**`verbose_log_level`** `int` — default: 30

Numerical [log level](https://docs.python.org/3/library/logging.html#logging-levels) used when checking a feature flag fails.

---

**`cache`** `BaseCache` — default: None

Custom cache implementation that extends `UnleashClient.cache.BaseCache`. See [custom cache](#custom-cache). When unset, the SDK uses an on-disk cache.

---

**`scheduler`** `BaseScheduler` — default: 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`** `str` — default: None

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

---

**`multiple_instance_mode`** `InstanceAllowType` — default: WARN

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

---

**`event_callback`** `Callable` — default: None

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

---

**`sdk_flavor`** `str` — default: 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`** `str` — default: 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.

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

### Check variants

Use `get_variant` to evaluate a flag with [variants](/concepts/feature-flag-variants). The method returns a dictionary:

```python
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`:

#### Flag disabled or not found

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

#### Flag enabled, no variant resolved

The flag is enabled but no variant applies, for example when the flag has no variants configured:

```python
{
    "name": "disabled",
    "enabled": False,
    "feature_enabled": True,
}
```

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:

```python
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](/concepts/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:

```python
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

> **Note**
>
> Gradual rollout strategies require you to pass either a `userId` or a `sessionId` for [stickiness](/concepts/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.

#### Sync

```python
client.destroy()
```

#### Async

SDK v6.9.0+

```python
await 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](/concepts/activation-strategies). You can also define [custom activation strategies](/concepts/custom-activation-strategies) if the built-in ones don't cover your needs.

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

```python
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:

#### Sync

```python
# 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,
)
```

#### Async

SDK v6.9.0+

```python
# Requires a custom strategy defined in Unleash called 'EmailStrategy'
my_custom_strategies = {
    "EmailStrategy": ActiveForEmailStrategy()
}

client = AsyncUnleashClient(
    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 type     | Description                                                                                                                                     |
| -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| `READY`        | The SDK loaded feature flags from the Unleash server or the local cache. Emitted at least once.                                                 |
| `FETCHED`      | A 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_FLAG` | A flag with [impression data](/concepts/impression-data) enabled was evaluated with `is_enabled`.                                               |
| `VARIANT`      | A 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.

> **Note**
>
> The SDK does not include a built-in event bus. The example below uses [Blinker](https://pypi.org/project/blinker/) to send signals.

#### Sync

```python
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")
```

#### Async

SDK v6.9.0+

```python
from blinker import signal
from UnleashClient import AsyncUnleashClient
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 = AsyncUnleashClient(
    url="https://<your-unleash-instance>/api",
    app_name="my-python-app",
    custom_headers={"Authorization": "<your-backend-token>"},
    event_callback=example_callback,
)
await client.initialize_client()
client.is_enabled("my-flag")
```

### Impression data

When [impression data](/concepts/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:

```python
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](/concepts/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:

#### Sync

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

#### Async

SDK v6.9.0+

```python
from UnleashClient import AsyncUnleashClient

client = AsyncUnleashClient(
    url="https://<your-unleash-instance>/api",
    app_name="my-python-app",
    custom_headers={"Authorization": "<your-backend-token>"},
)
await 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

#### Counter

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

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

#### Gauge

Use gauges for values that can go up or down, such as current memory usage or active user count.

```python
client.impact_metrics.define_gauge("active_users", "Current number of active users")
client.impact_metrics.update_gauge("active_users", 42)
```

#### Histogram

Histograms measure distributions such as latency or payload size.

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

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.

#### Dictionary

```python
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,
)
```

#### File

```python
from pathlib import Path
from UnleashClient.cache import FileCache
from UnleashClient import UnleashClient

cache = FileCache("my-cache")
cache.bootstrap_from_file(Path("/path/to/your_bootstrap.json"))

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

#### URL

```python
from UnleashClient.cache import FileCache
from UnleashClient import UnleashClient

cache = FileCache("my-cache")
cache.bootstrap_from_url(
    "https://your-server/bootstrap.json",
    headers={"Authorization": "<token>"},
)

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

The `headers` argument is optional; use it when the bootstrap URL requires authentication. You can also set `request_timeout`, in seconds, for the bootstrap request.

## Local caching and offline behavior

The SDK persists the latest flag configuration to an on-disk cache between restarts, using [fcache](https://pypi.org/project/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:

```python
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:

```python
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](#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](https://uwsgi-docs.readthedocs.io/en/latest/articles/TheArtOfGracefulReloading.html#preforking-vs-lazy-apps-vs-lazy) 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.

```python
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:

```python
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](https://github.com/Unleash/unleash-python-sdk/blob/main/v6_MIGRATION_GUIDE.md) before upgrading.

## Troubleshooting

#### All flags evaluate to false

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](/concepts/unleash-context) fields match your rollout strategy and constraints.

#### Flags never update in uWSGI or Celery workers

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](#framework-integration) for details.