Skip to content

fix: Report a distinct User-Agent for the async client - #516

Merged
jsonbailey merged 1 commit into
mainfrom
jb/sdk-2658/async-user-agent
Sep 4, 2026
Merged

jsonbailey merged 1 commit into
mainfrom
jb/sdk-2658/async-user-agent

Conversation

@jsonbailey

@jsonbailey jsonbailey commented Sep 3, 2026 •

Copy link
Copy Markdown
Contributor

Overview

The async client (AsyncLDClient) shipped reusing the synchronous PythonClient/<version> User-Agent, so LaunchDarkly cannot distinguish async traffic from sync. Because sync and async ship in the same package, the User-Agent token is the signal that tells them apart.

This adds a distinct async token PythonAsyncClient and threads it through every async request path.

Changes

  • impl/http.py: add SYNC_USER_AGENT / ASYNC_USER_AGENT constants; _base_headers(config, user_agent=SYNC_USER_AGENT).
  • impl/util.py: _headers(config, user_agent=SYNC_USER_AGENT) passes the token through.
  • Async request sites now pass ASYNC_USER_AGENT:
    • impl/aio/transport.py — AsyncSSEFactory streaming headers
    • impl/datasource/async_feature_requester.py
    • impl/datasourcev2/async_polling.py
    • impl/events/async_event_processor.py
  • .sdk_metadata.json: register the token on the existing python-server-sdk entry → userAgents: ["PythonClient", "PythonAsyncClient"].

Sync behavior is unchanged (the default token is still PythonClient).

Design note

SDK-2658 originally proposed a separate python-server-sdk-async sdk-meta registry entry. We chose to keep a single entry and add the async token to its userAgents array instead, because async has no independent package and is not a separately-tracked feature.

The analytics/event SDK name python-server-sdk-async already ships (async_client.py); billing confirmed it does not key off the User-Agent, so this change is safe and additive.

Tests

New testing/impl/test_user_agent.py locks the exact wire tokens for both sync and async via _base_headers / _headers. mypy / isort / pycodestyle clean; touched-component suites pass.

Implements SDK-2658.


Note

Overview
Async AsyncLDClient traffic previously used the same PythonClient/<version> User-Agent as the sync client, so LaunchDarkly could not tell them apart in one package. This PR introduces PythonAsyncClient and wires it through shared header helpers while keeping sync defaults unchanged.

SYNC_USER_AGENT / ASYNC_USER_AGENT are defined in impl/http.py; _base_headers and _headers take an optional user_agent (default PythonClient). Async paths pass ASYNC_USER_AGENT: SSE in impl/aio/transport.py, FDv1 polling in async_feature_requester.py, FDv2 polling in async_polling.py, and event posts in async_event_processor.py.

.sdk_metadata.json adds PythonAsyncClient to the existing python-server-sdk userAgents list. testing/impl/test_user_agent.py asserts the exact wire User-Agent strings for sync and async.

Reviewed by Cursor Bugbot for commit 32498ce. Bugbot is set up for automated code reviews on this repo. Configure here.

The async client shipped reusing the sync 'PythonClient/<version>' User-Agent,
so LaunchDarkly could not tell async traffic apart from sync. Since sync and
async ship in one package, the User-Agent token is the distinguishing signal.

Add an ASYNC_USER_AGENT token 'PythonAsyncClient' and thread it through the
async request paths (AsyncSSEFactory streaming headers, and the async feature
requester, FDv2 polling, and event processor request headers) by parameterizing
the shared _base_headers / _headers helpers. Sync behavior is unchanged.

Register the new token in .sdk_metadata.json under the existing python-server-sdk
entry, since async has no independent package.

Implements SDK-2658. The analytics/event name 'python-server-sdk-async' already
ships; billing confirmed it does not key off the User-Agent.
@jsonbailey jsonbailey changed the title feat: Report a distinct User-Agent for the async client fix: Report a distinct User-Agent for the async client Sep 3, 2026
@jsonbailey
jsonbailey marked this pull request as ready for review September 3, 2026 14:31
@jsonbailey
jsonbailey requested a review from a team as a code owner September 3, 2026 14:31
@jsonbailey
jsonbailey merged commit 514c467 into main Sep 4, 2026
18 checks passed
@jsonbailey
jsonbailey deleted the jb/sdk-2658/async-user-agent branch September 4, 2026 20:46
jsonbailey added a commit that referenced this pull request Sep 25, 2026
🤖 I have created a release *beep* *boop*
---


##
[9.18.0](9.17.0...9.18.0)
(2026-09-25)


### Features

* Add retry state for RETRY-spec backoff
([#522](#522))
([e734558](e734558))
* Drive repeating tasks from a delay source
([9abe8cb](9abe8cb))
* Retry indefinitely after a data source failure instead of stopping
permanently in FDv1
([e17e173](e17e173))
* warn that payload filtering has no effect with FDv2
([#518](#518))
([296311f](296311f))
* Warn when the SDK is used in a forked process without postfork
([#512](#512))
([75402e9](75402e9))


### Bug Fixes

* Add async modules to the readthedocs API reference
([#513](#513))
([f40d3b2](f40d3b2))
* Log the cached-data evaluation warning only once per client
([#520](#520))
([59ce998](59ce998))
* Read all items when a collection is empty
([#524](#524))
([1087147](1087147))
* Repeating task wait duration is no longer reduced by a slow callback
duration.
([9abe8cb](9abe8cb))
* Report a distinct User-Agent for the async client
([#516](#516))
([514c467](514c467))
* Warn and use the documented default for an invalid poll interval or
initial reconnect delay
([e17e173](e17e173))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Overview**
> This PR **cuts release 9.18.0** by bumping the package version in
`pyproject.toml`, `ldclient/version.py`,
`.release-please-manifest.json`, and the provenance docs example, and by
adding the **9.18.0** section to `CHANGELOG.md`.
> 
> There is **no application code in the diff**—Release Please is
packaging work that was already merged. The new changelog highlights
**FDv1 data-source resilience** (indefinite retry after failures,
RETRY-spec backoff state, repeating tasks driven by a delay source, and
sane defaults/warnings for invalid poll/reconnect settings),
**operational warnings** (forked process without `postfork`, FDv2
payload filtering), and **fixes** (empty-collection reads,
repeating-task timing, one-time cached-data warnings, distinct async
`User-Agent`, ReadTheDocs async API docs).
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
eef08a1. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Jason Bailey <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants