From f9b7c14c3deac6ea00069a2961ee6de704835a91 Mon Sep 17 00:00:00 2001 From: iscai-msft Date: Mon, 27 Apr 2026 13:33:17 -0400 Subject: [PATCH 1/6] fix: convert custom etag-typed headers to etag/match_condition Custom headers typed as Azure.Core.eTag (e.g. x-ms-blob-if-match, x-ms-source-if-match) now get the same etag/match_condition treatment as standard If-Match/If-None-Match headers. The original wire name is preserved via originalWireName so the correct HTTP header is sent. - Add isEtag flag in emitter for Azure.Core.eTag-typed headers - Add _is_etag_match/_is_etag_none_match helpers in preprocessor - Preserve originalWireName in headers_convert for custom headers - Use originalWireName in serializer for dynamic header name - Use identity-based removal for etag param reordering Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- ...-etagMatchCondition-2026-04-27-13-33-08.md | 7 +++ .../http-client-python/emitter/src/http.ts | 15 +++++- .../pygen/codegen/models/parameter.py | 1 + .../serializers/parameter_serializer.py | 33 +++++++++---- .../generator/pygen/preprocess/__init__.py | 47 ++++++++++++++++--- 5 files changed, 85 insertions(+), 18 deletions(-) create mode 100644 .chronus/changes/python-etagMatchCondition-2026-04-27-13-33-08.md diff --git a/.chronus/changes/python-etagMatchCondition-2026-04-27-13-33-08.md b/.chronus/changes/python-etagMatchCondition-2026-04-27-13-33-08.md new file mode 100644 index 00000000000..be12b4f1143 --- /dev/null +++ b/.chronus/changes/python-etagMatchCondition-2026-04-27-13-33-08.md @@ -0,0 +1,7 @@ +--- +changeKind: fix +packages: + - "@typespec/http-client-python" +--- + +Convert custom etag-typed headers (e.g. `x-ms-blob-if-match`) to `etag`/`match_condition` parameters, matching the standard `If-Match`/`If-None-Match` behavior while preserving the original wire name for the HTTP header diff --git a/packages/http-client-python/emitter/src/http.ts b/packages/http-client-python/emitter/src/http.ts index e87e8b1f113..98e24398298 100644 --- a/packages/http-client-python/emitter/src/http.ts +++ b/packages/http-client-python/emitter/src/http.ts @@ -1,4 +1,4 @@ -import { NoTarget } from "@typespec/compiler"; +import { getNamespaceFullName, NoTarget } from "@typespec/compiler"; import { getHttpOperationParameter, @@ -19,6 +19,7 @@ import { SdkQueryParameter, SdkServiceMethod, SdkServiceResponseHeader, + SdkType, UsageFlags, } from "@azure-tools/typespec-client-generator-core"; import { HttpStatusCodeRange } from "@typespec/http"; @@ -41,6 +42,17 @@ export enum ReferredByOperationTypes { NonPagingOnly = 2, } +function isEtagType(type: SdkType): boolean { + if (type.kind === "nullable") return isEtagType(type.type); + const raw = type.__raw; + if (!raw || raw.kind !== "Scalar") return false; + return ( + raw.name === "eTag" && + raw.namespace !== undefined && + getNamespaceFullName(raw.namespace) === "Azure.Core" + ); +} + function isContentTypeParameter(parameter: SdkHeaderParameter) { return parameter.serializedName.toLowerCase() === "content-type"; } @@ -496,6 +508,7 @@ function emitHttpHeaderParameter( delimiter, explode, clientDefaultValue, + isEtag: isEtagType(parameter.type), }; } diff --git a/packages/http-client-python/generator/pygen/codegen/models/parameter.py b/packages/http-client-python/generator/pygen/codegen/models/parameter.py index 26eb0ba5c9a..3bc9d7d2f49 100644 --- a/packages/http-client-python/generator/pygen/codegen/models/parameter.py +++ b/packages/http-client-python/generator/pygen/codegen/models/parameter.py @@ -61,6 +61,7 @@ def __init__( ) -> None: super().__init__(yaml_data, code_model) self.wire_name: str = yaml_data.get("wireName", "") + self.original_wire_name: str = yaml_data.get("originalWireName", self.wire_name) self.client_name: str = self.yaml_data["clientName"] self.optional: bool = self.yaml_data["optional"] self.implementation: Optional[str] = yaml_data.get("implementation", None) diff --git a/packages/http-client-python/generator/pygen/codegen/serializers/parameter_serializer.py b/packages/http-client-python/generator/pygen/codegen/serializers/parameter_serializer.py index 9091f86748d..9b37025ff3f 100644 --- a/packages/http-client-python/generator/pygen/codegen/serializers/parameter_serializer.py +++ b/packages/http-client-python/generator/pygen/codegen/serializers/parameter_serializer.py @@ -39,16 +39,6 @@ class PopKwargType(Enum): "client-request-id": [], "x-ms-client-request-id": [], "return-client-request-id": [], - "etag": [ - """if_match = prep_if_match(etag, match_condition)""", - """if if_match is not None:""", - """ _headers["If-Match"] = _SERIALIZER.header("if_match", if_match, "str")""", - ], - "match-condition": [ - """if_none_match = prep_if_none_match(etag, match_condition)""", - """if if_none_match is not None:""", - """ _headers["If-None-Match"] = _SERIALIZER.header("if_none_match", if_none_match, "str")""", - ], } @@ -157,6 +147,29 @@ def serialize_query_header( ): return SPECIAL_HEADER_SERIALIZATION[param.wire_name.lower()] + if ( + not is_legacy + and param.location == ParameterLocation.HEADER + and param.wire_name.lower() in ("etag", "match-condition") + ): + # Build etag serialization using the original wire name for the + # actual HTTP header. For standard If-Match / If-None-Match the + # original wire name equals those values; for custom etag headers + # (e.g. x-ms-blob-if-match) it carries the real header name. + header_name = param.original_wire_name + if param.wire_name.lower() == "etag": + return [ + """if_match = prep_if_match(etag, match_condition)""", + """if if_match is not None:""", + f""" _headers["{header_name}"] = _SERIALIZER.header("if_match", if_match, "str")""", + ] + else: + return [ + """if_none_match = prep_if_none_match(etag, match_condition)""", + """if if_none_match is not None:""", + f""" _headers["{header_name}"] = _SERIALIZER.header("if_none_match", if_none_match, "str")""", + ] + set_parameter = "_{}['{}'] = {}".format( kwarg_name, param.wire_name, diff --git a/packages/http-client-python/generator/pygen/preprocess/__init__.py b/packages/http-client-python/generator/pygen/preprocess/__init__.py index 6d3344059a3..6306449dadc 100644 --- a/packages/http-client-python/generator/pygen/preprocess/__init__.py +++ b/packages/http-client-python/generator/pygen/preprocess/__init__.py @@ -166,8 +166,29 @@ def get_wire_name_lower(parameter: dict[str, Any]) -> str: return (parameter.get("wireName") or "").lower() +def _is_etag_match(parameter: dict[str, Any]) -> bool: + """Return True if this header should fill the if-match etag slot.""" + wire = get_wire_name_lower(parameter) + if wire == "if-match": + return True + return bool(parameter.get("isEtag")) and "none-match" not in wire + + +def _is_etag_none_match(parameter: dict[str, Any]) -> bool: + """Return True if this header should fill the if-none-match etag slot.""" + wire = get_wire_name_lower(parameter) + if wire == "if-none-match": + return True + return bool(parameter.get("isEtag")) and "none-match" in wire + + def headers_convert(yaml_data: dict[str, Any], replace_data: Any) -> None: if isinstance(replace_data, dict): + # Preserve original wire name so the serializer can use it + # for the actual HTTP header (important for custom etag headers + # like x-ms-blob-if-match). + if "wireName" in replace_data and "wireName" in yaml_data: + yaml_data["originalWireName"] = yaml_data["wireName"] for k, v in replace_data.items(): yaml_data[k] = v @@ -317,9 +338,9 @@ def update_client(self, yaml_data: dict[str, Any]) -> None: if p["location"] == "header" and wire_name_lower == "client-request-id": yaml_data["requestIdHeaderName"] = wire_name_lower if self.version_tolerant and p["location"] == "header": - if wire_name_lower == "if-match": + if _is_etag_match(p) and not property_if_match: property_if_match = p - elif wire_name_lower == "if-none-match": + elif _is_etag_none_match(p) and not property_if_none_match: property_if_none_match = p # pylint: disable=line-too-long # some service(e.g. https://github.com/Azure/azure-rest-api-specs/blob/main/specification/cosmos-db/data-plane/Microsoft.Tables/preview/2019-02-02/table.json) @@ -333,11 +354,11 @@ def update_client(self, yaml_data: dict[str, Any]) -> None: if property_if_match and property_if_none_match: # arrange if-match and if-none-match to the end of parameters - o["parameters"] = [ - item - for item in o["parameters"] - if get_wire_name_lower(item) not in ("if-match", "if-none-match") - ] + [property_if_match, property_if_none_match] + etag_params = {id(property_if_match), id(property_if_none_match)} + o["parameters"] = [item for item in o["parameters"] if id(item) not in etag_params] + [ + property_if_match, + property_if_none_match, + ] o["hasEtag"] = True yaml_data["hasEtag"] = True @@ -374,6 +395,18 @@ def update_parameter(self, yaml_data: dict[str, Any]) -> None: yaml_data["hideInMethod"] = True if self.version_tolerant and yaml_data["location"] == "header" and wire_name_lower in HEADERS_CONVERT_IN_METHOD: headers_convert(yaml_data, HEADERS_CONVERT_IN_METHOD[wire_name_lower]) + elif ( + self.version_tolerant + and yaml_data["location"] == "header" + and yaml_data.get("isEtag") + and wire_name_lower not in HEADERS_CONVERT_IN_METHOD + ): + # Custom etag-typed headers (e.g. x-ms-blob-if-match) get the same + # etag/match_condition treatment as standard If-Match/If-None-Match. + if _is_etag_none_match(yaml_data): + headers_convert(yaml_data, HEADERS_CONVERT_IN_METHOD["if-none-match"]) + else: + headers_convert(yaml_data, HEADERS_CONVERT_IN_METHOD["if-match"]) if wire_name_lower in ["$host", "content-type", "accept"] and yaml_data["type"]["type"] == "constant": yaml_data["clientDefaultValue"] = yaml_data["type"]["value"] From c1fdbb4bc5d0ed885ad6f639fd81518a4d8da228 Mon Sep 17 00:00:00 2001 From: iscai-msft Date: Mon, 27 Apr 2026 14:10:54 -0400 Subject: [PATCH 2/6] simplify check --- .../http-client-python/emitter/src/http.ts | 14 +++- .../pygen/codegen/models/parameter.py | 2 +- .../serializers/parameter_serializer.py | 25 ++---- .../generator/pygen/preprocess/__init__.py | 84 ++++++++----------- 4 files changed, 57 insertions(+), 68 deletions(-) diff --git a/packages/http-client-python/emitter/src/http.ts b/packages/http-client-python/emitter/src/http.ts index 98e24398298..0a695e68c6e 100644 --- a/packages/http-client-python/emitter/src/http.ts +++ b/packages/http-client-python/emitter/src/http.ts @@ -53,6 +53,18 @@ function isEtagType(type: SdkType): boolean { ); } +function getEtagRole(parameter: SdkHeaderParameter): string | undefined { + if (!isEtagType(parameter.type)) return undefined; + const name = parameter.name.toLowerCase(); + const wire = parameter.serializedName.toLowerCase(); + // Check client name first, then wire name + if (name.includes("nonematch") || name.includes("none_match")) return "ifNoneMatch"; + if (name.includes("match")) return "ifMatch"; + if (wire.endsWith("if-none-match") || wire === "if-none-match") return "ifNoneMatch"; + if (wire.endsWith("if-match") || wire === "if-match") return "ifMatch"; + return undefined; +} + function isContentTypeParameter(parameter: SdkHeaderParameter) { return parameter.serializedName.toLowerCase() === "content-type"; } @@ -508,7 +520,7 @@ function emitHttpHeaderParameter( delimiter, explode, clientDefaultValue, - isEtag: isEtagType(parameter.type), + etagRole: getEtagRole(parameter), }; } diff --git a/packages/http-client-python/generator/pygen/codegen/models/parameter.py b/packages/http-client-python/generator/pygen/codegen/models/parameter.py index 3bc9d7d2f49..726756671ff 100644 --- a/packages/http-client-python/generator/pygen/codegen/models/parameter.py +++ b/packages/http-client-python/generator/pygen/codegen/models/parameter.py @@ -61,7 +61,7 @@ def __init__( ) -> None: super().__init__(yaml_data, code_model) self.wire_name: str = yaml_data.get("wireName", "") - self.original_wire_name: str = yaml_data.get("originalWireName", self.wire_name) + self.etag_role: Optional[str] = yaml_data.get("etagRole", None) self.client_name: str = self.yaml_data["clientName"] self.optional: bool = self.yaml_data["optional"] self.implementation: Optional[str] = yaml_data.get("implementation", None) diff --git a/packages/http-client-python/generator/pygen/codegen/serializers/parameter_serializer.py b/packages/http-client-python/generator/pygen/codegen/serializers/parameter_serializer.py index 9b37025ff3f..9cfbdc94a16 100644 --- a/packages/http-client-python/generator/pygen/codegen/serializers/parameter_serializer.py +++ b/packages/http-client-python/generator/pygen/codegen/serializers/parameter_serializer.py @@ -147,28 +147,19 @@ def serialize_query_header( ): return SPECIAL_HEADER_SERIALIZATION[param.wire_name.lower()] - if ( - not is_legacy - and param.location == ParameterLocation.HEADER - and param.wire_name.lower() in ("etag", "match-condition") - ): - # Build etag serialization using the original wire name for the - # actual HTTP header. For standard If-Match / If-None-Match the - # original wire name equals those values; for custom etag headers - # (e.g. x-ms-blob-if-match) it carries the real header name. - header_name = param.original_wire_name - if param.wire_name.lower() == "etag": + if not is_legacy and param.location == ParameterLocation.HEADER and param.etag_role is not None: + header_name = param.wire_name + if param.etag_role == "ifMatch": return [ """if_match = prep_if_match(etag, match_condition)""", """if if_match is not None:""", f""" _headers["{header_name}"] = _SERIALIZER.header("if_match", if_match, "str")""", ] - else: - return [ - """if_none_match = prep_if_none_match(etag, match_condition)""", - """if if_none_match is not None:""", - f""" _headers["{header_name}"] = _SERIALIZER.header("if_none_match", if_none_match, "str")""", - ] + return [ + """if_none_match = prep_if_none_match(etag, match_condition)""", + """if if_none_match is not None:""", + f""" _headers["{header_name}"] = _SERIALIZER.header("if_none_match", if_none_match, "str")""", + ] set_parameter = "_{}['{}'] = {}".format( kwarg_name, diff --git a/packages/http-client-python/generator/pygen/preprocess/__init__.py b/packages/http-client-python/generator/pygen/preprocess/__init__.py index 6306449dadc..4c7a4263d4b 100644 --- a/packages/http-client-python/generator/pygen/preprocess/__init__.py +++ b/packages/http-client-python/generator/pygen/preprocess/__init__.py @@ -132,20 +132,18 @@ def update_paging_response(yaml_data: dict[str, Any]) -> None: "client-request-id", "return-client-request-id", ) -HEADERS_CONVERT_IN_METHOD = { - "if-match": { - "clientName": "etag", - "wireName": "etag", - "description": "check if resource is changed. Set None to skip checking etag.", - }, - "if-none-match": { - "clientName": "match_condition", - "wireName": "match-condition", - "description": "The match condition to use upon the etag.", - "type": { - "type": "sdkcore", - "name": "MatchConditions", - }, +ETAG_MATCH_DATA = { + "clientName": "etag", + "etagRole": "ifMatch", + "description": "check if resource is changed. Set None to skip checking etag.", +} +ETAG_NONE_MATCH_DATA = { + "clientName": "match_condition", + "etagRole": "ifNoneMatch", + "description": "The match condition to use upon the etag.", + "type": { + "type": "sdkcore", + "name": "MatchConditions", }, } CLOUD_SETTING = { @@ -166,29 +164,25 @@ def get_wire_name_lower(parameter: dict[str, Any]) -> str: return (parameter.get("wireName") or "").lower() -def _is_etag_match(parameter: dict[str, Any]) -> bool: - """Return True if this header should fill the if-match etag slot.""" - wire = get_wire_name_lower(parameter) - if wire == "if-match": - return True - return bool(parameter.get("isEtag")) and "none-match" not in wire - +def _get_etag_role(parameter: dict[str, Any]) -> Optional[str]: + """Return 'ifMatch', 'ifNoneMatch', or None for this header parameter. -def _is_etag_none_match(parameter: dict[str, Any]) -> bool: - """Return True if this header should fill the if-none-match etag slot.""" + The emitter sets etagRole directly for TypeSpec inputs. For autorest/swagger + inputs we fall back to the wire name. + """ + role = parameter.get("etagRole") + if role: + return role wire = get_wire_name_lower(parameter) + if wire == "if-match": + return "ifMatch" if wire == "if-none-match": - return True - return bool(parameter.get("isEtag")) and "none-match" in wire + return "ifNoneMatch" + return None def headers_convert(yaml_data: dict[str, Any], replace_data: Any) -> None: if isinstance(replace_data, dict): - # Preserve original wire name so the serializer can use it - # for the actual HTTP header (important for custom etag headers - # like x-ms-blob-if-match). - if "wireName" in replace_data and "wireName" in yaml_data: - yaml_data["originalWireName"] = yaml_data["wireName"] for k, v in replace_data.items(): yaml_data[k] = v @@ -302,9 +296,8 @@ def update_types(self, yaml_data: list[dict[str, Any]]) -> None: value["name"] = upper_name # add type for reference - for v in HEADERS_CONVERT_IN_METHOD.values(): - if isinstance(v, dict) and "type" in v: - yaml_data.append(v["type"]) + if "type" in ETAG_NONE_MATCH_DATA: + yaml_data.append(ETAG_NONE_MATCH_DATA["type"]) yaml_data.append(CLOUD_SETTING["type"]) # type: ignore def update_client(self, yaml_data: dict[str, Any]) -> None: @@ -338,9 +331,10 @@ def update_client(self, yaml_data: dict[str, Any]) -> None: if p["location"] == "header" and wire_name_lower == "client-request-id": yaml_data["requestIdHeaderName"] = wire_name_lower if self.version_tolerant and p["location"] == "header": - if _is_etag_match(p) and not property_if_match: + role = _get_etag_role(p) + if role == "ifMatch" and not property_if_match: property_if_match = p - elif _is_etag_none_match(p) and not property_if_none_match: + elif role == "ifNoneMatch" and not property_if_none_match: property_if_none_match = p # pylint: disable=line-too-long # some service(e.g. https://github.com/Azure/azure-rest-api-specs/blob/main/specification/cosmos-db/data-plane/Microsoft.Tables/preview/2019-02-02/table.json) @@ -393,20 +387,12 @@ def update_parameter(self, yaml_data: dict[str, Any]) -> None: wire_name_lower in HEADERS_HIDE_IN_METHOD or yaml_data.get("clientDefaultValue") == "multipart/form-data" ): yaml_data["hideInMethod"] = True - if self.version_tolerant and yaml_data["location"] == "header" and wire_name_lower in HEADERS_CONVERT_IN_METHOD: - headers_convert(yaml_data, HEADERS_CONVERT_IN_METHOD[wire_name_lower]) - elif ( - self.version_tolerant - and yaml_data["location"] == "header" - and yaml_data.get("isEtag") - and wire_name_lower not in HEADERS_CONVERT_IN_METHOD - ): - # Custom etag-typed headers (e.g. x-ms-blob-if-match) get the same - # etag/match_condition treatment as standard If-Match/If-None-Match. - if _is_etag_none_match(yaml_data): - headers_convert(yaml_data, HEADERS_CONVERT_IN_METHOD["if-none-match"]) - else: - headers_convert(yaml_data, HEADERS_CONVERT_IN_METHOD["if-match"]) + if self.version_tolerant and yaml_data["location"] == "header": + role = _get_etag_role(yaml_data) + if role == "ifMatch": + headers_convert(yaml_data, ETAG_MATCH_DATA) + elif role == "ifNoneMatch": + headers_convert(yaml_data, ETAG_NONE_MATCH_DATA) if wire_name_lower in ["$host", "content-type", "accept"] and yaml_data["type"]["type"] == "constant": yaml_data["clientDefaultValue"] = yaml_data["type"]["value"] From c23753c8a442820ab9697c45d4832177d2dfb590 Mon Sep 17 00:00:00 2001 From: iscai-msft Date: Mon, 27 Apr 2026 14:12:42 -0400 Subject: [PATCH 3/6] don't special case for swagger generations --- .../generator/pygen/preprocess/__init__.py | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/packages/http-client-python/generator/pygen/preprocess/__init__.py b/packages/http-client-python/generator/pygen/preprocess/__init__.py index 4c7a4263d4b..2fc89c5defa 100644 --- a/packages/http-client-python/generator/pygen/preprocess/__init__.py +++ b/packages/http-client-python/generator/pygen/preprocess/__init__.py @@ -165,20 +165,8 @@ def get_wire_name_lower(parameter: dict[str, Any]) -> str: def _get_etag_role(parameter: dict[str, Any]) -> Optional[str]: - """Return 'ifMatch', 'ifNoneMatch', or None for this header parameter. - - The emitter sets etagRole directly for TypeSpec inputs. For autorest/swagger - inputs we fall back to the wire name. - """ - role = parameter.get("etagRole") - if role: - return role - wire = get_wire_name_lower(parameter) - if wire == "if-match": - return "ifMatch" - if wire == "if-none-match": - return "ifNoneMatch" - return None + """Return 'ifMatch', 'ifNoneMatch', or None for this header parameter.""" + return parameter.get("etagRole") def headers_convert(yaml_data: dict[str, Any], replace_data: Any) -> None: From e3b61cf6589e87098a8beaebcb5e2fb03df1b5c6 Mon Sep 17 00:00:00 2001 From: iscai-msft Date: Mon, 27 Apr 2026 14:43:16 -0400 Subject: [PATCH 4/6] refactor: clean up etag role detection in emitter Move etagRole determination into the emitter's getEtagRole() function. Standard If-Match/If-None-Match headers work with any type. Non-standard headers (e.g. x-ms-blob-if-match) require Azure.Core.eTag type. Simplify preprocess _get_etag_role to just read the etagRole field. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- packages/http-client-python/emitter/src/http.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/http-client-python/emitter/src/http.ts b/packages/http-client-python/emitter/src/http.ts index 0a695e68c6e..07104c972ef 100644 --- a/packages/http-client-python/emitter/src/http.ts +++ b/packages/http-client-python/emitter/src/http.ts @@ -54,14 +54,17 @@ function isEtagType(type: SdkType): boolean { } function getEtagRole(parameter: SdkHeaderParameter): string | undefined { - if (!isEtagType(parameter.type)) return undefined; const name = parameter.name.toLowerCase(); const wire = parameter.serializedName.toLowerCase(); - // Check client name first, then wire name + // Standard If-Match / If-None-Match headers work with any type + if (wire === "if-match") return "ifMatch"; + if (wire === "if-none-match") return "ifNoneMatch"; + // Non-standard headers require Azure.Core.eTag type + if (!isEtagType(parameter.type)) return undefined; if (name.includes("nonematch") || name.includes("none_match")) return "ifNoneMatch"; if (name.includes("match")) return "ifMatch"; - if (wire.endsWith("if-none-match") || wire === "if-none-match") return "ifNoneMatch"; - if (wire.endsWith("if-match") || wire === "if-match") return "ifMatch"; + if (wire.endsWith("-if-none-match")) return "ifNoneMatch"; + if (wire.endsWith("-if-match")) return "ifMatch"; return undefined; } From a1fc7a609005fa47fe15541e1698826a15559d78 Mon Sep 17 00:00:00 2001 From: iscai-msft Date: Mon, 27 Apr 2026 14:45:12 -0400 Subject: [PATCH 5/6] chore: update changeset description Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../changes/python-etagMatchCondition-2026-04-27-13-33-08.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.chronus/changes/python-etagMatchCondition-2026-04-27-13-33-08.md b/.chronus/changes/python-etagMatchCondition-2026-04-27-13-33-08.md index be12b4f1143..b5352021d82 100644 --- a/.chronus/changes/python-etagMatchCondition-2026-04-27-13-33-08.md +++ b/.chronus/changes/python-etagMatchCondition-2026-04-27-13-33-08.md @@ -4,4 +4,4 @@ packages: - "@typespec/http-client-python" --- -Convert custom etag-typed headers (e.g. `x-ms-blob-if-match`) to `etag`/`match_condition` parameters, matching the standard `If-Match`/`If-None-Match` behavior while preserving the original wire name for the HTTP header +Support custom wire names for etags defined with `Azure.Core.eTag` (e.g. `x-ms-blob-if-match`) From e327a0de575812e60bca6fd31ddefb1f580f81ce Mon Sep 17 00:00:00 2001 From: iscai-msft Date: Tue, 28 Apr 2026 11:15:52 -0400 Subject: [PATCH 6/6] fix duplicate arg issue --- .../http-client-python/generator/pygen/preprocess/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/http-client-python/generator/pygen/preprocess/__init__.py b/packages/http-client-python/generator/pygen/preprocess/__init__.py index 2fc89c5defa..bc595e1748a 100644 --- a/packages/http-client-python/generator/pygen/preprocess/__init__.py +++ b/packages/http-client-python/generator/pygen/preprocess/__init__.py @@ -330,9 +330,11 @@ def update_client(self, yaml_data: dict[str, Any]) -> None: if not property_if_match and property_if_none_match: property_if_match = property_if_none_match.copy() property_if_match["wireName"] = "if-match" + property_if_match["etagRole"] = "ifMatch" if not property_if_none_match and property_if_match: property_if_none_match = property_if_match.copy() property_if_none_match["wireName"] = "if-none-match" + property_if_none_match["etagRole"] = "ifNoneMatch" if property_if_match and property_if_none_match: # arrange if-match and if-none-match to the end of parameters