From 00e965530a0d709d89278e817a3de11a2fb5d378 Mon Sep 17 00:00:00 2001 From: ubaskota <19787410+ubaskota@users.noreply.github.com> Date: Thu, 26 Mar 2026 14:21:13 -0400 Subject: [PATCH 1/2] Remove enum usage for header/trailer in smithy-http and aws-sdk-signers --- .../src/aws_sdk_signers/_http.py | 6 +-- .../src/aws_sdk_signers/interfaces/http.py | 29 ++++--------- .../aws-sdk-signers/tests/unit/test_fields.py | 41 +++++++++---------- .../smithy-http/src/smithy_http/__init__.py | 10 ++--- .../src/smithy_http/aio/aiohttp.py | 3 +- .../smithy-http/src/smithy_http/aio/crt.py | 6 +-- .../src/smithy_http/interfaces/__init__.py | 29 ++++--------- .../smithy-http/tests/unit/test_fields.py | 41 +++++++++---------- 8 files changed, 68 insertions(+), 97 deletions(-) diff --git a/packages/aws-sdk-signers/src/aws_sdk_signers/_http.py b/packages/aws-sdk-signers/src/aws_sdk_signers/_http.py index 97ceb61e8..40e14a89d 100644 --- a/packages/aws-sdk-signers/src/aws_sdk_signers/_http.py +++ b/packages/aws-sdk-signers/src/aws_sdk_signers/_http.py @@ -36,7 +36,7 @@ def __init__( *, name: str, values: Iterable[str] | None = None, - kind: interfaces_http.FieldPosition = interfaces_http.FieldPosition.HEADER, + kind: interfaces_http.FieldPosition = "header", ): self.name = name self.values: list[str] = list(values) if values is not None else [] @@ -92,7 +92,7 @@ def __eq__(self, other: object) -> bool: return False return ( self.name == other.name - and self.kind is other.kind + and self.kind == other.kind and self.values == other.values ) @@ -168,7 +168,7 @@ def get_by_type( Used to grab all headers or all trailers. """ - return [entry for entry in self.entries.values() if entry.kind is kind] + return [entry for entry in self.entries.values() if entry.kind == kind] def extend(self, other: interfaces_http.Fields) -> None: """Merges ``entries`` of ``other`` into the current ``entries``. diff --git a/packages/aws-sdk-signers/src/aws_sdk_signers/interfaces/http.py b/packages/aws-sdk-signers/src/aws_sdk_signers/interfaces/http.py index 8d450ceb8..01e516e67 100644 --- a/packages/aws-sdk-signers/src/aws_sdk_signers/interfaces/http.py +++ b/packages/aws-sdk-signers/src/aws_sdk_signers/interfaces/http.py @@ -5,29 +5,18 @@ from collections import OrderedDict from collections.abc import AsyncIterable, Iterable, Iterator -from enum import Enum -from typing import Protocol, runtime_checkable +from typing import Literal, Protocol, runtime_checkable +FieldPosition = Literal["header", "trailer"] +"""The type of a field. -class FieldPosition(Enum): - """The type of a field. +Defines its placement in a request or response. - Defines its placement in a request or response. - """ - - HEADER = 0 - """Header field. - - In HTTP this is a header as defined in RFC 9110 Section 6.3. Implementations of - other protocols may use this FieldPosition for similar types of metadata. - """ - - TRAILER = 1 - """Trailer field. +header: Header field. In HTTP this is a header as defined in RFC 9110 Section 6.3. +trailer: Trailer field. In HTTP this is a trailer as defined in RFC 9110 Section 6.5. - In HTTP this is a trailer as defined in RFC 9110 Section 6.5. Implementations of - other protocols may use this FieldPosition for similar types of metadata. - """ +Implementations of other protocols may use this FieldPosition for similar types of metadata. +""" class Field(Protocol): @@ -43,7 +32,7 @@ class Field(Protocol): name: str values: list[str] - kind: FieldPosition = FieldPosition.HEADER + kind: FieldPosition = "header" def add(self, value: str) -> None: """Append a value to a field.""" diff --git a/packages/aws-sdk-signers/tests/unit/test_fields.py b/packages/aws-sdk-signers/tests/unit/test_fields.py index 2731f100d..cfd68d615 100644 --- a/packages/aws-sdk-signers/tests/unit/test_fields.py +++ b/packages/aws-sdk-signers/tests/unit/test_fields.py @@ -3,22 +3,21 @@ import pytest from aws_sdk_signers import Field, Fields -from aws_sdk_signers.interfaces.http import FieldPosition def test_field_single_valued_basics() -> None: - field = Field(name="fname", values=["fval"], kind=FieldPosition.HEADER) + field = Field(name="fname", values=["fval"], kind="header") assert field.name == "fname" - assert field.kind == FieldPosition.HEADER + assert field.kind == "header" assert field.values == ["fval"] assert field.as_string() == "fval" assert field.as_tuples() == [("fname", "fval")] def test_field_multi_valued_basics() -> None: - field = Field(name="fname", values=["fval1", "fval2"], kind=FieldPosition.HEADER) + field = Field(name="fname", values=["fval1", "fval2"], kind="header") assert field.name == "fname" - assert field.kind == FieldPosition.HEADER + assert field.kind == "header" assert field.values == ["fval1", "fval2"] assert field.as_string() == "fval1,fval2" assert field.as_tuples() == [("fname", "fval1"), ("fname", "fval2")] @@ -62,16 +61,16 @@ def test_field_serialization(values: list[str], expected: str) -> None: "field,expected_repr", [ ( - Field(name="fname", values=["fval1", "fval2"], kind=FieldPosition.HEADER), - "Field(name='fname', value=['fval1', 'fval2'], kind=)", + Field(name="fname", values=["fval1", "fval2"], kind="header"), + "Field(name='fname', value=['fval1', 'fval2'], kind='header')", ), ( - Field(name="fname", kind=FieldPosition.TRAILER), - "Field(name='fname', value=[], kind=)", + Field(name="fname", kind="trailer"), + "Field(name='fname', value=[], kind='trailer')", ), ( Field(name="fname"), - "Field(name='fname', value=[], kind=)", + "Field(name='fname', value=[], kind='header')", ), ], ) @@ -83,8 +82,8 @@ def test_field_repr(field: Field, expected_repr: str) -> None: "f1,f2", [ ( - Field(name="fname", values=["fval1", "fval2"], kind=FieldPosition.TRAILER), - Field(name="fname", values=["fval1", "fval2"], kind=FieldPosition.TRAILER), + Field(name="fname", values=["fval1", "fval2"], kind="trailer"), + Field(name="fname", values=["fval1", "fval2"], kind="trailer"), ), ( Field(name="fname", values=["fval1", "fval2"]), @@ -104,20 +103,20 @@ def test_field_equality(f1: Field, f2: Field) -> None: "f1,f2", [ ( - Field(name="fname", values=["fval1", "fval2"], kind=FieldPosition.HEADER), - Field(name="fname", values=["fval1", "fval2"], kind=FieldPosition.TRAILER), + Field(name="fname", values=["fval1", "fval2"], kind="header"), + Field(name="fname", values=["fval1", "fval2"], kind="trailer"), ), ( - Field(name="fname", values=["fval1", "fval2"], kind=FieldPosition.HEADER), - Field(name="fname", values=["fval2", "fval1"], kind=FieldPosition.HEADER), + Field(name="fname", values=["fval1", "fval2"], kind="header"), + Field(name="fname", values=["fval2", "fval1"], kind="header"), ), ( - Field(name="fname", values=["fval1", "fval2"], kind=FieldPosition.HEADER), - Field(name="fname", values=["fval1"], kind=FieldPosition.HEADER), + Field(name="fname", values=["fval1", "fval2"], kind="header"), + Field(name="fname", values=["fval1"], kind="header"), ), ( - Field(name="fname1", values=["fval1", "fval2"], kind=FieldPosition.HEADER), - Field(name="fname2", values=["fval1", "fval2"], kind=FieldPosition.HEADER), + Field(name="fname1", values=["fval1", "fval2"], kind="header"), + Field(name="fname2", values=["fval1", "fval2"], kind="header"), ), ], ) @@ -211,7 +210,7 @@ def test_fields_length_value(fields: Fields, expected_length: int) -> None: Fields([Field(name="fname1")]), ( "Fields(OrderedDict({'fname1': Field(name='fname1', value=[], " - "kind=)}))" + "kind='header')}))" ), ), ], diff --git a/packages/smithy-http/src/smithy_http/__init__.py b/packages/smithy-http/src/smithy_http/__init__.py index 5f6aea018..2deba068d 100644 --- a/packages/smithy-http/src/smithy_http/__init__.py +++ b/packages/smithy-http/src/smithy_http/__init__.py @@ -24,7 +24,7 @@ def __init__( *, name: str, values: Iterable[str] | None = None, - kind: FieldPosition = FieldPosition.HEADER, + kind: FieldPosition = "header", ): self.name = name self.values: list[str] = list(values) if values is not None else [] @@ -79,7 +79,7 @@ def __eq__(self, other: object) -> bool: return False return ( self.name == other.name - and self.kind is other.kind + and self.kind == other.kind and self.values == other.values ) @@ -153,7 +153,7 @@ def get_by_type(self, kind: FieldPosition) -> list[interfaces.Field]: Used to grab all headers or all trailers. """ - return [entry for entry in self.entries.values() if entry.kind is kind] + return [entry for entry in self.entries.values() if entry.kind == kind] def extend(self, other: interfaces.Fields) -> None: """Merges ``entries`` of ``other`` into the current ``entries``. @@ -225,8 +225,6 @@ def tuples_to_fields( try: fields[name].add(value) except KeyError: - fields[name] = Field( - name=name, values=[value], kind=kind or FieldPosition.HEADER - ) + fields[name] = Field(name=name, values=[value], kind=kind or "header") return fields diff --git a/packages/smithy-http/src/smithy_http/aio/aiohttp.py b/packages/smithy-http/src/smithy_http/aio/aiohttp.py index 2d5114726..3bfcbbeb3 100644 --- a/packages/smithy-http/src/smithy_http/aio/aiohttp.py +++ b/packages/smithy-http/src/smithy_http/aio/aiohttp.py @@ -28,7 +28,6 @@ from .. import Field, Fields from ..interfaces import ( - FieldPosition, HTTPClientConfiguration, HTTPRequestConfiguration, ) @@ -125,7 +124,7 @@ async def _marshal_response( headers[header_name] = Field( name=header_name, values=[header_val], - kind=FieldPosition.HEADER, + kind="header", ) return HTTPResponse( diff --git a/packages/smithy-http/src/smithy_http/aio/crt.py b/packages/smithy-http/src/smithy_http/aio/crt.py index 5e5cc861c..fa8d19609 100644 --- a/packages/smithy-http/src/smithy_http/aio/crt.py +++ b/packages/smithy-http/src/smithy_http/aio/crt.py @@ -41,7 +41,6 @@ from .. import Field, Fields from .. import interfaces as http_interfaces from ..exceptions import SmithyHTTPError -from ..interfaces import FieldPosition from . import interfaces as http_aio_interfaces # Default buffer size for reading from streams (8 KB) @@ -203,7 +202,7 @@ async def _await_response( fields[header_name] = Field( name=header_name, values=[header_val], - kind=FieldPosition.HEADER, + kind="header", ) return AWSCRTHTTPResponse( status=status_code, @@ -294,8 +293,7 @@ def _marshal_request( request.fields.set_field(Field(name="accept", values=["*/*"])) for fld in request.fields.entries.values(): - # TODO: Use literal values for "header"/"trailer". - if fld.kind.value != FieldPosition.HEADER.value: + if fld.kind != "header": continue for val in fld.values: headers_list.append((fld.name, val)) diff --git a/packages/smithy-http/src/smithy_http/interfaces/__init__.py b/packages/smithy-http/src/smithy_http/interfaces/__init__.py index f524ceed4..3664ab17f 100644 --- a/packages/smithy-http/src/smithy_http/interfaces/__init__.py +++ b/packages/smithy-http/src/smithy_http/interfaces/__init__.py @@ -2,29 +2,18 @@ # SPDX-License-Identifier: Apache-2.0 from collections.abc import Iterator from dataclasses import dataclass -from enum import Enum -from typing import Protocol +from typing import Literal, Protocol +FieldPosition = Literal["header", "trailer"] +"""The type of a field. -class FieldPosition(Enum): - """The type of a field. +Defines its placement in a request or response. - Defines its placement in a request or response. - """ - - HEADER = 0 - """Header field. - - In HTTP this is a header as defined in RFC 9110 Section 6.3. Implementations of - other protocols may use this FieldPosition for similar types of metadata. - """ - - TRAILER = 1 - """Trailer field. +header: Header field. In HTTP this is a header as defined in RFC 9110 Section 6.3. +trailer: Trailer field. In HTTP this is a trailer as defined in RFC 9110 Section 6.5. - In HTTP this is a trailer as defined in RFC 9110 Section 6.5. Implementations of - other protocols may use this FieldPosition for similar types of metadata. - """ +Implementations of other protocols may use this FieldPosition for similar types of metadata. +""" class Field(Protocol): @@ -40,7 +29,7 @@ class Field(Protocol): name: str values: list[str] - kind: FieldPosition = FieldPosition.HEADER + kind: FieldPosition = "header" def add(self, value: str) -> None: """Append a value to a field.""" diff --git a/packages/smithy-http/tests/unit/test_fields.py b/packages/smithy-http/tests/unit/test_fields.py index 884c9d105..5cefd2537 100644 --- a/packages/smithy-http/tests/unit/test_fields.py +++ b/packages/smithy-http/tests/unit/test_fields.py @@ -6,22 +6,21 @@ import pytest from smithy_http import Field, Fields -from smithy_http.interfaces import FieldPosition def test_field_single_valued_basics() -> None: - field = Field(name="fname", values=["fval"], kind=FieldPosition.HEADER) + field = Field(name="fname", values=["fval"], kind="header") assert field.name == "fname" - assert field.kind == FieldPosition.HEADER + assert field.kind == "header" assert field.values == ["fval"] assert field.as_string() == "fval" assert field.as_tuples() == [("fname", "fval")] def test_field_multi_valued_basics() -> None: - field = Field(name="fname", values=["fval1", "fval2"], kind=FieldPosition.HEADER) + field = Field(name="fname", values=["fval1", "fval2"], kind="header") assert field.name == "fname" - assert field.kind == FieldPosition.HEADER + assert field.kind == "header" assert field.values == ["fval1", "fval2"] assert field.as_string() == "fval1, fval2" assert field.as_tuples() == [("fname", "fval1"), ("fname", "fval2")] @@ -65,16 +64,16 @@ def test_field_serialization(values: list[str], expected: str): "field,expected_repr", [ ( - Field(name="fname", values=["fval1", "fval2"], kind=FieldPosition.HEADER), - "Field(name='fname', value=['fval1', 'fval2'], kind=)", + Field(name="fname", values=["fval1", "fval2"], kind="header"), + "Field(name='fname', value=['fval1', 'fval2'], kind='header')", ), ( - Field(name="fname", kind=FieldPosition.TRAILER), - "Field(name='fname', value=[], kind=)", + Field(name="fname", kind="trailer"), + "Field(name='fname', value=[], kind='trailer')", ), ( Field(name="fname"), - "Field(name='fname', value=[], kind=)", + "Field(name='fname', value=[], kind='header')", ), ], ) @@ -86,8 +85,8 @@ def test_field_repr(field: Field, expected_repr: str) -> None: "f1,f2", [ ( - Field(name="fname", values=["fval1", "fval2"], kind=FieldPosition.TRAILER), - Field(name="fname", values=["fval1", "fval2"], kind=FieldPosition.TRAILER), + Field(name="fname", values=["fval1", "fval2"], kind="trailer"), + Field(name="fname", values=["fval1", "fval2"], kind="trailer"), ), ( Field(name="fname", values=["fval1", "fval2"]), @@ -107,20 +106,20 @@ def test_field_equality(f1: Field, f2: Field) -> None: "f1,f2", [ ( - Field(name="fname", values=["fval1", "fval2"], kind=FieldPosition.HEADER), - Field(name="fname", values=["fval1", "fval2"], kind=FieldPosition.TRAILER), + Field(name="fname", values=["fval1", "fval2"], kind="header"), + Field(name="fname", values=["fval1", "fval2"], kind="trailer"), ), ( - Field(name="fname", values=["fval1", "fval2"], kind=FieldPosition.HEADER), - Field(name="fname", values=["fval2", "fval1"], kind=FieldPosition.HEADER), + Field(name="fname", values=["fval1", "fval2"], kind="header"), + Field(name="fname", values=["fval2", "fval1"], kind="header"), ), ( - Field(name="fname", values=["fval1", "fval2"], kind=FieldPosition.HEADER), - Field(name="fname", values=["fval1"], kind=FieldPosition.HEADER), + Field(name="fname", values=["fval1", "fval2"], kind="header"), + Field(name="fname", values=["fval1"], kind="header"), ), ( - Field(name="fname1", values=["fval1", "fval2"], kind=FieldPosition.HEADER), - Field(name="fname2", values=["fval1", "fval2"], kind=FieldPosition.HEADER), + Field(name="fname1", values=["fval1", "fval2"], kind="header"), + Field(name="fname2", values=["fval1", "fval2"], kind="header"), ), ], ) @@ -214,7 +213,7 @@ def test_fields_length_value(fields: Fields, expected_length: int) -> None: Fields([Field(name="fname1")]), ( "Fields(OrderedDict({'fname1': Field(name='fname1', value=[], " - "kind=)}))" + "kind='header')}))" ), ), ], From 172a38f14cd8f486ad08ba66560f4ef34249860a Mon Sep 17 00:00:00 2001 From: ubaskota <19787410+ubaskota@users.noreply.github.com> Date: Fri, 27 Mar 2026 12:12:41 -0400 Subject: [PATCH 2/2] Add a changelog --- ...sdk-signers-breaking-9b52e1c29eb541999e2239be4c725d00.json | 4 ++++ ...smithy-http-breaking-f567cc46e12d4f61ac5f85b65a0e4426.json | 4 ++++ 2 files changed, 8 insertions(+) create mode 100644 packages/aws-sdk-signers/.changes/next-release/aws-sdk-signers-breaking-9b52e1c29eb541999e2239be4c725d00.json create mode 100644 packages/smithy-http/.changes/next-release/smithy-http-breaking-f567cc46e12d4f61ac5f85b65a0e4426.json diff --git a/packages/aws-sdk-signers/.changes/next-release/aws-sdk-signers-breaking-9b52e1c29eb541999e2239be4c725d00.json b/packages/aws-sdk-signers/.changes/next-release/aws-sdk-signers-breaking-9b52e1c29eb541999e2239be4c725d00.json new file mode 100644 index 000000000..5e7faca7b --- /dev/null +++ b/packages/aws-sdk-signers/.changes/next-release/aws-sdk-signers-breaking-9b52e1c29eb541999e2239be4c725d00.json @@ -0,0 +1,4 @@ +{ + "type": "breaking", + "description": "Replace FieldPosition enum with a Literal type" +} diff --git a/packages/smithy-http/.changes/next-release/smithy-http-breaking-f567cc46e12d4f61ac5f85b65a0e4426.json b/packages/smithy-http/.changes/next-release/smithy-http-breaking-f567cc46e12d4f61ac5f85b65a0e4426.json new file mode 100644 index 000000000..5e7faca7b --- /dev/null +++ b/packages/smithy-http/.changes/next-release/smithy-http-breaking-f567cc46e12d4f61ac5f85b65a0e4426.json @@ -0,0 +1,4 @@ +{ + "type": "breaking", + "description": "Replace FieldPosition enum with a Literal type" +}