feat: add async feature flag evaluation - #900
Conversation
posthog-python Compliance ReportDate: 2026-08-26 14:53:06 UTC ✅ All Tests Passed!111/111 tests passed Capture_V1 Tests✅ 94/94 tests passed View Details
Feature_Flags Tests✅ 17/17 tests passed View Details
|
Prompt To Fix All With AI### Issue 1
posthog/test/test_async_request.py:151-173
**Incomplete retry-flow coverage**
The success-path test covers only one transient failure before success, while `async_flags` supports multiple attempts and attempt-dependent backoff. A regression in later retries or exponential backoff progression would therefore pass this suite undetected.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.Reviews (1): Last reviewed commit: "fix: evaluate flags for zero distinct ID" | Re-trigger Greptile |
…ags-v2 # Conflicts: # posthog/async_client.py
dustinbyrne
left a comment
There was a problem hiding this comment.
Agent-led review, human-reviewed before posting.
| "/flags/?v=2", content=data, headers=headers, timeout=timeout | ||
| ) | ||
| return await asyncio.to_thread(_process_flags_response, response) | ||
| except (httpx_module.TimeoutException, httpx_module.NetworkError): |
There was a problem hiding this comment.
httpx.RemoteProtocolError is a TransportError, but it is not a NetworkError. A server disconnect can therefore skip the configured retry. evaluate_flags() then catches the error and returns an empty snapshot. I reproduced this with feature_flags_request_max_retries=1: the first request raised RemoteProtocolError, the second would have succeeded, but the client made only one request. Could we include this transient error in the bounded retry policy and add a disconnect-then-success test?
💡 Motivation and Context
Continues the async SDK work for #103. This PR is stacked on #899, supersedes #720, and incorporates its open review feedback.
The new
await AsyncPosthog.evaluate_flags(...)method sends a non-blocking/flagsrequest and returns the existingFeatureFlagEvaluationssnapshot. Itsis_enabled(),get_flag(), andget_flag_payload()accessors remain synchronous because they only read the returned snapshot. Passing that snapshot tocapture(flags=...)attaches the exact values used for application branching.This PR intentionally does not add the deprecated single-flag client methods or deprecated
capture(send_feature_flags=...)behavior. It also does not add local flag-definition polling or fallback caches. The initial async API focuses on the reported problem: remote evaluation when local evaluation is unavailable. This avoids synchronous Redis access, poller lifecycle failures, and the deferred flag-called-event races reported on #720.$feature_flag_calledevents use the synchronous bufferedcapture()method from #899. Deduplication therefore happens before control returns without scheduling extra tasks. The client also tracks flag and remote-config requests as in-flight operations so shutdown waits before closing its instance-owned HTTP transport.await get_remote_config_payload(...)provides the corresponding non-blocking remote-config API with bearer authentication and encoded request paths.All changes remain additive. Existing synchronous clients and module-level APIs are unchanged. The new constructor options are appended to the new async client's signature.
playgrounds/fastapi-async-clientdemonstrates the complete stacked API in FastAPI, including buffered and immediate capture, identify operations, exception capture, flag evaluation, remote config, flush, and lifespan-managed shutdown.💚 How did you test it?
uv run ruff format --check .uv run ruff check .uv run mypy --no-site-packages --config-file mypy.ini . | uv run mypy-baseline filteruv run --extra test pytest --timeout=30 -q- 2298 passed, 15 skipped, 36 subtests passeduv run --extra dev make public_api_checkuv lock --checkuv run python -W error -c "import posthog"posthoganalyticsmirror with the async flag APIsorigin/feat/async-client-capture-v2reported no actionable findings at0b40643📝 Checklist
If releasing new changes
sampo addto generate a changeset file🤖 Agent context
Autonomy: Human-driven (agent-assisted)
Implemented with pi. We narrowed the old #720 API to the recommended snapshot-based evaluation method and remote config. Prior review findings guided the retry contract, request sanitization, shutdown tracking, flag-called deduplication, and the decision to omit deprecated and local-evaluation surfaces from this stack.