Problem
The Python generator merges request-header dicts case-sensitively. Forwarding content-type: application/json into a generated JSON operation adds Content-Type: application/json; HTTPX exposes application/json, application/json. Defaults and authentication can cause similar conflicts. The shared serializers also mutate supplied dicts, although public Pydantic-decorated calls may copy them first.
Originally observed with 7.25.0 and reproduced on master a39800cd. The shared code affects urllib3, asyncio/aiohttp, httpx and httpx2. Transport handling also differs: lower-case form Content-Type can be misclassified as JSON by urllib3, while aiohttp adds another Content-Type.
Minimal reproduction
Using a generated Petstore client:
from petstore_api import ApiClient, Pet, PetApi
headers = {"content-type": "application/json"}
request = PetApi(ApiClient())._add_pet_serialize(
pet=Pet(name="test", photoUrls=[]), _headers=headers,
_content_type=None, _request_auth=None, _host_index=0,
)
print(request[2]) # contains both content-type and Content-Type
print(headers) # serializer added generated/default headers
Expected: one effective Content-Type and unchanged input. An HTTPX call with the same _headers produces the duplicate wire values described above.
Proposed correction
PR #25163 applies a common case-insensitive, request-local merge across these four libraries, with native header containers in each transport. Existing same-spelling precedence is retained: per-call → explicit/generated headers → defaults → client cookie → authentication. Later dict entries win; default-header setters replace earlier case variants. Legitimate comma-separated values and repeated transport fields are preserved. Public validation is unchanged.
Regression coverage includes precedence, authentication, input preservation, concurrent calls and lower-case form/multipart handling.
Related: TS/JS #6571, Go #24766, Go follow-up #24791. The latter informed the compatibility and deterministic-precedence approach. Searches of open/closed Python header and HTTPX issues/PRs did not find an equivalent fix in the results reviewed; this does not claim no other report exists.
Problem
The Python generator merges request-header dicts case-sensitively. Forwarding
content-type: application/jsoninto a generated JSON operation addsContent-Type: application/json; HTTPX exposesapplication/json, application/json. Defaults and authentication can cause similar conflicts. The shared serializers also mutate supplied dicts, although public Pydantic-decorated calls may copy them first.Originally observed with 7.25.0 and reproduced on master
a39800cd. The shared code affects urllib3, asyncio/aiohttp, httpx and httpx2. Transport handling also differs: lower-case form Content-Type can be misclassified as JSON by urllib3, while aiohttp adds another Content-Type.Minimal reproduction
Using a generated Petstore client:
Expected: one effective Content-Type and unchanged input. An HTTPX call with the same
_headersproduces the duplicate wire values described above.Proposed correction
PR #25163 applies a common case-insensitive, request-local merge across these four libraries, with native header containers in each transport. Existing same-spelling precedence is retained: per-call → explicit/generated headers → defaults → client cookie → authentication. Later dict entries win; default-header setters replace earlier case variants. Legitimate comma-separated values and repeated transport fields are preserved. Public validation is unchanged.
Regression coverage includes precedence, authentication, input preservation, concurrent calls and lower-case form/multipart handling.
Related: TS/JS #6571, Go #24766, Go follow-up #24791. The latter informed the compatibility and deterministic-precedence approach. Searches of open/closed Python header and HTTPX issues/PRs did not find an equivalent fix in the results reviewed; this does not claim no other report exists.