diff --git a/.chronus/changes/fix-required-etag-header-2026-09-14.md b/.chronus/changes/fix-required-etag-header-2026-09-14.md new file mode 100644 index 00000000000..46d5b15cc0d --- /dev/null +++ b/.chronus/changes/fix-required-etag-header-2026-09-14.md @@ -0,0 +1,8 @@ +--- +changeKind: fix +packages: + - "@typespec/http-client-python" +--- + +Generate required `If-Match` headers as direct `if_match` parameters instead of +the optional `etag` and `match_condition` convenience API. diff --git a/packages/http-client-python/emitter/src/http.ts b/packages/http-client-python/emitter/src/http.ts index e1fe7efb351..4831c8e9474 100644 --- a/packages/http-client-python/emitter/src/http.ts +++ b/packages/http-client-python/emitter/src/http.ts @@ -206,7 +206,11 @@ function isEtagType(type: SdkType): boolean { ); } -function getEtagRole(parameter: SdkHeaderParameter): string | undefined { +export function getEtagRole(parameter: SdkHeaderParameter): string | undefined { + // The etag/match_condition convenience API can omit or redirect the wire + // header, so it is only valid when the TypeSpec header itself is optional. + if (!parameter.optional) return undefined; + const name = parameter.name.toLowerCase(); const wire = parameter.serializedName.toLowerCase(); // Standard If-Match / If-None-Match headers work with any type diff --git a/packages/http-client-python/emitter/test/http.test.ts b/packages/http-client-python/emitter/test/http.test.ts new file mode 100644 index 00000000000..536d57d50c8 --- /dev/null +++ b/packages/http-client-python/emitter/test/http.test.ts @@ -0,0 +1,19 @@ +import type { SdkHeaderParameter } from "@azure-tools/typespec-client-generator-core"; +import { strictEqual } from "assert"; +import { describe, it } from "vitest"; +import { getEtagRole } from "../src/http.js"; + +function createHeaderParameter(optional: boolean): SdkHeaderParameter { + return { + name: "ifMatch", + serializedName: "If-Match", + optional, + } as SdkHeaderParameter; +} + +describe("typespec-python: HTTP parameters", () => { + it("only promotes optional If-Match headers to the conditional convenience API", () => { + strictEqual(getEtagRole(createHeaderParameter(true)), "ifMatch"); + strictEqual(getEtagRole(createHeaderParameter(false)), undefined); + }); +}); diff --git a/packages/http-client-python/tests/mock_api/azure/asynctests/test_azure_special_headers_conditional_request_async.py b/packages/http-client-python/tests/mock_api/azure/asynctests/test_azure_special_headers_conditional_request_async.py index 264cc71cdb3..e1f7ae821b9 100644 --- a/packages/http-client-python/tests/mock_api/azure/asynctests/test_azure_special_headers_conditional_request_async.py +++ b/packages/http-client-python/tests/mock_api/azure/asynctests/test_azure_special_headers_conditional_request_async.py @@ -21,6 +21,11 @@ async def test_post_if_match(client: ConditionalRequestClient): await client.post_if_match(etag="valid", match_condition=MatchConditions.IfNotModified) +@pytest.mark.asyncio +async def test_post_required_if_match(client: ConditionalRequestClient): + await client.post_required_if_match(if_match='"required"') + + @pytest.mark.asyncio async def test_post_if_none_match(client: ConditionalRequestClient): await client.post_if_none_match(etag="invalid", match_condition=MatchConditions.IfModified) diff --git a/packages/http-client-python/tests/mock_api/azure/test_azure_special_headers_conditional_request.py b/packages/http-client-python/tests/mock_api/azure/test_azure_special_headers_conditional_request.py index dacd7ba07f0..a54cc1baa75 100644 --- a/packages/http-client-python/tests/mock_api/azure/test_azure_special_headers_conditional_request.py +++ b/packages/http-client-python/tests/mock_api/azure/test_azure_special_headers_conditional_request.py @@ -19,6 +19,10 @@ def test_post_if_match(client: ConditionalRequestClient): client.post_if_match(etag="valid", match_condition=MatchConditions.IfNotModified) +def test_post_required_if_match(client: ConditionalRequestClient): + client.post_required_if_match(if_match='"required"') + + def test_post_if_none_match(client: ConditionalRequestClient): client.post_if_none_match(etag="invalid", match_condition=MatchConditions.IfModified)