You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
## Overview
Part of the async Python SDK work (epic SDK-60). Adds the async FDv2
**data system** (coordinator) and wires it into the async client.
Targets `main` (its predecessor, #485 async FDv2 data sources, has
merged).
This is experimental and should not be considered production-ready.
## What this PR adds
- `impl/datasystem/async_fdv2.py` — `AsyncFDv2`, the async data system
that coordinates the async initializers and synchronizers, mirrors the
sync `FDv2` fallback/recovery behavior, and exposes the async data
source status and flag tracking. Includes
`AsyncFeatureStoreClientWrapper` for persistent-store availability
polling.
- `async_client.py` wiring: `_make_data_system` builds `AsyncFDv2`;
`_wire_data_source_sessions` shares the client's aiohttp session into
the async data source builders so they reuse the connection pool.
## Async readiness gate (awaitable end-to-end)
Readiness and availability are now awaitable so a persistent store
populated by another process (warm start / daemon) is recognized:
- `AsyncDataSystem.data_availability` and
`AsyncLDClient.is_initialized()` are coroutines; the eval path awaits
`data_availability()` directly.
- `AsyncFeatureStore` gains an abstract async `is_initialized()` (with
`$inited` caching + monotonic latch in `AsyncCachingStoreWrapper`,
surfaced via `AsyncStore.is_ready()`). The `getattr` duck-type is
dropped, so a custom store that omits it fails at construction rather
than silently serving defaults.
- The gate catches a persistent-store error and degrades to `DEFAULTS`
rather than raising, so `variation()` never throws on a store outage.
(Sync counterpart: #506.)
## Wrapper hardening
- `is_monitoring_enabled()` delegates to the store's own opt-in
(matching sync), so a store that cannot report availability is not
polled and left stuck unavailable.
- `close()` is idempotent, bounds the availability-poller stop with a
timeout, and logs errors from the inner store close.
- `AsyncStore.close()` logs and swallows close errors instead of
returning them.
## Shared refactor
- `impl/datasystem/fdv2_common.py` gains module-level
`fallback_condition` / `recovery_condition`, shared by the sync and
async data systems.
## Testing
- `LD_SKIP_DATABASE_TESTS=1 uv run pytest
ldclient/testing/impl/datasystem/` and
`ldclient/testing/test_async_client.py` — green.
- `make lint` (mypy, isort, pycodestyle) — clean.
Tracked internally: SDK-2870
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> **Overview**
> Adds **async Flag Delivery v2** support by introducing `AsyncFDv2`,
wiring it into `AsyncLDClient` when `datasystem_config` is set
(replacing the previous `NotImplementedError`), and sharing the client's
aiohttp session with async polling/streaming data source builders via
`_wire_data_source_sessions`.
>
> **Readiness and availability are now async** so persistent stores
populated by another process can be detected:
`AsyncLDClient.is_initialized()`, `AsyncDataSystem.data_availability()`,
and a new `AsyncFeatureStore.is_initialized()` (with caching/latching in
`AsyncCachingStoreWrapper` and `AsyncStore.is_ready()`). Contract-test
harnesses await these checks accordingly.
>
> Sync **FDv2** is refactored to share wiring through `_FDv2Base` and
module-level `fallback_condition` / `recovery_condition` in
`fdv2_common.py`. `AsyncFDv2` mirrors initializer/synchronizer
coordination, FDv1 fallback, persistent-store outage recovery, and adds
`AsyncFeatureStoreClientWrapper` for availability polling.
>
> `AsyncStore.close()` logs and swallows close errors instead of
returning them. Large new test suites cover `AsyncFDv2`, async
persistence, and readiness gating.
>
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
111bb37. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
log.warning("Feature Flag evaluation attempted before client has initialized - using last known values from feature store for feature key: "+key)
461
500
else:
462
501
log.warning("Feature Flag evaluation attempted before client has initialized! Feature store unavailable - returning default: "+str(default) +" for feature key: "+key)
0 commit comments