Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,12 @@ The recommended pattern:

#### Redis example

A complete working example written in Python using Redis with distributed locking is available in the [posthog-python repository](https://github.com/PostHog/posthog-python/blob/master/examples/redis_flag_cache.py). It implements the locking pattern described above.
Complete working examples written in Python using Redis with distributed locking are available in the PostHog Python repository:

- [Synchronous Redis cache provider](https://github.com/PostHog/posthog-python/blob/a91ca2b479b8bd74b1192a67887a1396cf92e385/examples/redis_flag_cache.py)
- [Async Redis cache provider](https://github.com/PostHog/posthog-python/blob/a91ca2b479b8bd74b1192a67887a1396cf92e385/examples/async_redis_flag_cache.py) for async-first applications using `redis.asyncio`

They implement the locking pattern described above.

### Caches without locking

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,24 +11,37 @@ from posthog import FlagDefinitionCacheProvider, FlagDefinitionCacheData
To create a custom cache, implement the `FlagDefinitionCacheProvider` protocol:

```python
from typing import Awaitable, Optional, Protocol, Union


class FlagDefinitionCacheProvider(Protocol):
def get_flag_definitions(self) -> Optional[FlagDefinitionCacheData]:
def get_flag_definitions(
self,
) -> Union[
Optional[FlagDefinitionCacheData], Awaitable[Optional[FlagDefinitionCacheData]]
]:
"""Retrieve cached flag definitions."""
...

def should_fetch_flag_definitions(self) -> bool:
def should_fetch_flag_definitions(self) -> Union[bool, Awaitable[bool]]:
"""Determine if this instance should fetch new definitions."""
...

def on_flag_definitions_received(self, data: FlagDefinitionCacheData) -> None:
def on_flag_definitions_received(
self, data: FlagDefinitionCacheData
) -> Optional[Awaitable[None]]:
"""Store definitions after a successful fetch."""
...

def shutdown(self) -> None:
def shutdown(self) -> Optional[Awaitable[None]]:
"""Clean up resources on shutdown."""
...
```

You can implement these methods as either sync or async methods. If a method is async, the SDK waits for it to finish before continuing.

> **Note:** If you use an async client that is tied to an event loop, such as `redis.asyncio`, use a client dedicated to this cache provider. The SDK runs async cache provider methods on its own background event loop.

When the SDK fetches flag definitions from the API, it passes a `FlagDefinitionCacheData` object to `on_flag_definitions_received()` for you to store:

```python
Expand All @@ -42,12 +55,12 @@ class FlagDefinitionCacheData(TypedDict):

| Method | Purpose | Return value |
| :----- | :------ | :----------- |
| `get_flag_definitions()` | Retrieve cached definitions. Called when the poller refreshes. | Cached data, or `None` if cache is empty |
| `should_fetch_flag_definitions()` | Decide if this instance should fetch. Use for distributed coordination (e.g., locks). | `True` to fetch, `False` to skip |
| `on_flag_definitions_received(data)` | Store definitions after a successful API fetch. | None |
| `shutdown()` | Release locks, close connections, clean up resources. | None |
| `get_flag_definitions()` | Retrieve cached definitions. Called when the poller refreshes. | Cached data or `None`; async implementations return the same after being awaited |
| `should_fetch_flag_definitions()` | Decide if this instance should fetch. Use for distributed coordination (e.g., locks). | `True` to fetch or `False` to skip; async implementations return the same after being awaited |
| `on_flag_definitions_received(data)` | Store definitions after a successful API fetch. | `None`; async implementations return `None` after being awaited |
| `shutdown()` | Release locks, close connections, clean up resources. | `None`; async implementations return `None` after being awaited |

> **Note:** All methods may throw errors. The SDK catches and logs them gracefully, ensuring cache provider errors never break flag evaluation.
> **Note:** It is safe for cache provider implementations to raise exceptions. The SDK catches provider exceptions and continues without re-raising them: `should_fetch_flag_definitions()` falls back to fetching, `get_flag_definitions()` falls back to the API, and `on_flag_definitions_received()` or `shutdown()` failures do not stop flag evaluation or shutdown.

## Using your cache provider

Expand Down
Loading