Skip to content

Commit b2fad24

Browse files
authored
Remove enum usage for header/trailer in smithy-http and aws-sdk-signers (#670)
* Remove enum usage for header/trailer in smithy-http and aws-sdk-signers
1 parent 5c74c46 commit b2fad24

10 files changed

Lines changed: 96 additions & 98 deletions

File tree

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"type": "breaking",
3+
"description": "Replace `FieldPosition` enum values with string literals for `Field.kind`. Use \"header\" and \"trailer\" instead of `FieldPosition.HEADER` and `FieldPosition.TRAILER`."
4+
}

packages/aws-sdk-signers/src/aws_sdk_signers/_http.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,13 @@
1515
from copy import deepcopy
1616
from dataclasses import dataclass
1717
from functools import cached_property
18-
from typing import TypedDict
18+
from typing import TypedDict, get_args
1919
from urllib.parse import urlunparse
2020

2121
import aws_sdk_signers.interfaces.http as interfaces_http
2222

23+
_VALID_FIELD_POSITIONS = frozenset(get_args(interfaces_http.FieldPosition))
24+
2325

2426
class Field(interfaces_http.Field):
2527
"""A name-value pair representing a single field in an HTTP Request or Response.
@@ -36,10 +38,12 @@ def __init__(
3638
*,
3739
name: str,
3840
values: Iterable[str] | None = None,
39-
kind: interfaces_http.FieldPosition = interfaces_http.FieldPosition.HEADER,
41+
kind: interfaces_http.FieldPosition = "header",
4042
):
4143
self.name = name
4244
self.values: list[str] = list(values) if values is not None else []
45+
if kind not in _VALID_FIELD_POSITIONS:
46+
raise ValueError(f"Unknown field kind: {kind!r}")
4347
self.kind = kind
4448

4549
def add(self, value: str) -> None:
@@ -92,7 +96,7 @@ def __eq__(self, other: object) -> bool:
9296
return False
9397
return (
9498
self.name == other.name
95-
and self.kind is other.kind
99+
and self.kind == other.kind
96100
and self.values == other.values
97101
)
98102

@@ -168,7 +172,7 @@ def get_by_type(
168172
169173
Used to grab all headers or all trailers.
170174
"""
171-
return [entry for entry in self.entries.values() if entry.kind is kind]
175+
return [entry for entry in self.entries.values() if entry.kind == kind]
172176

173177
def extend(self, other: interfaces_http.Fields) -> None:
174178
"""Merges ``entries`` of ``other`` into the current ``entries``.

packages/aws-sdk-signers/src/aws_sdk_signers/interfaces/http.py

Lines changed: 9 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,29 +5,18 @@
55

66
from collections import OrderedDict
77
from collections.abc import AsyncIterable, Iterable, Iterator
8-
from enum import Enum
9-
from typing import Protocol, runtime_checkable
8+
from typing import Literal, Protocol, runtime_checkable
109

10+
FieldPosition = Literal["header", "trailer"]
11+
"""The type of a field.
1112
12-
class FieldPosition(Enum):
13-
"""The type of a field.
13+
Defines its placement in a request or response.
1414
15-
Defines its placement in a request or response.
16-
"""
17-
18-
HEADER = 0
19-
"""Header field.
20-
21-
In HTTP this is a header as defined in RFC 9110 Section 6.3. Implementations of
22-
other protocols may use this FieldPosition for similar types of metadata.
23-
"""
24-
25-
TRAILER = 1
26-
"""Trailer field.
15+
header: Header field. In HTTP this is a header as defined in RFC 9110 Section 6.3.
16+
trailer: Trailer field. In HTTP this is a trailer as defined in RFC 9110 Section 6.5.
2717
28-
In HTTP this is a trailer as defined in RFC 9110 Section 6.5. Implementations of
29-
other protocols may use this FieldPosition for similar types of metadata.
30-
"""
18+
Implementations of other protocols may use this FieldPosition for similar types of metadata.
19+
"""
3120

3221

3322
class Field(Protocol):
@@ -43,7 +32,7 @@ class Field(Protocol):
4332

4433
name: str
4534
values: list[str]
46-
kind: FieldPosition = FieldPosition.HEADER
35+
kind: FieldPosition = "header"
4736

4837
def add(self, value: str) -> None:
4938
"""Append a value to a field."""

packages/aws-sdk-signers/tests/unit/test_fields.py

Lines changed: 25 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,21 @@
33

44
import pytest
55
from aws_sdk_signers import Field, Fields
6-
from aws_sdk_signers.interfaces.http import FieldPosition
76

87

98
def test_field_single_valued_basics() -> None:
10-
field = Field(name="fname", values=["fval"], kind=FieldPosition.HEADER)
9+
field = Field(name="fname", values=["fval"], kind="header")
1110
assert field.name == "fname"
12-
assert field.kind == FieldPosition.HEADER
11+
assert field.kind == "header"
1312
assert field.values == ["fval"]
1413
assert field.as_string() == "fval"
1514
assert field.as_tuples() == [("fname", "fval")]
1615

1716

1817
def test_field_multi_valued_basics() -> None:
19-
field = Field(name="fname", values=["fval1", "fval2"], kind=FieldPosition.HEADER)
18+
field = Field(name="fname", values=["fval1", "fval2"], kind="header")
2019
assert field.name == "fname"
21-
assert field.kind == FieldPosition.HEADER
20+
assert field.kind == "header"
2221
assert field.values == ["fval1", "fval2"]
2322
assert field.as_string() == "fval1,fval2"
2423
assert field.as_tuples() == [("fname", "fval1"), ("fname", "fval2")]
@@ -62,16 +61,16 @@ def test_field_serialization(values: list[str], expected: str) -> None:
6261
"field,expected_repr",
6362
[
6463
(
65-
Field(name="fname", values=["fval1", "fval2"], kind=FieldPosition.HEADER),
66-
"Field(name='fname', value=['fval1', 'fval2'], kind=<FieldPosition.HEADER: 0>)",
64+
Field(name="fname", values=["fval1", "fval2"], kind="header"),
65+
"Field(name='fname', value=['fval1', 'fval2'], kind='header')",
6766
),
6867
(
69-
Field(name="fname", kind=FieldPosition.TRAILER),
70-
"Field(name='fname', value=[], kind=<FieldPosition.TRAILER: 1>)",
68+
Field(name="fname", kind="trailer"),
69+
"Field(name='fname', value=[], kind='trailer')",
7170
),
7271
(
7372
Field(name="fname"),
74-
"Field(name='fname', value=[], kind=<FieldPosition.HEADER: 0>)",
73+
"Field(name='fname', value=[], kind='header')",
7574
),
7675
],
7776
)
@@ -83,8 +82,8 @@ def test_field_repr(field: Field, expected_repr: str) -> None:
8382
"f1,f2",
8483
[
8584
(
86-
Field(name="fname", values=["fval1", "fval2"], kind=FieldPosition.TRAILER),
87-
Field(name="fname", values=["fval1", "fval2"], kind=FieldPosition.TRAILER),
85+
Field(name="fname", values=["fval1", "fval2"], kind="trailer"),
86+
Field(name="fname", values=["fval1", "fval2"], kind="trailer"),
8887
),
8988
(
9089
Field(name="fname", values=["fval1", "fval2"]),
@@ -104,20 +103,20 @@ def test_field_equality(f1: Field, f2: Field) -> None:
104103
"f1,f2",
105104
[
106105
(
107-
Field(name="fname", values=["fval1", "fval2"], kind=FieldPosition.HEADER),
108-
Field(name="fname", values=["fval1", "fval2"], kind=FieldPosition.TRAILER),
106+
Field(name="fname", values=["fval1", "fval2"], kind="header"),
107+
Field(name="fname", values=["fval1", "fval2"], kind="trailer"),
109108
),
110109
(
111-
Field(name="fname", values=["fval1", "fval2"], kind=FieldPosition.HEADER),
112-
Field(name="fname", values=["fval2", "fval1"], kind=FieldPosition.HEADER),
110+
Field(name="fname", values=["fval1", "fval2"], kind="header"),
111+
Field(name="fname", values=["fval2", "fval1"], kind="header"),
113112
),
114113
(
115-
Field(name="fname", values=["fval1", "fval2"], kind=FieldPosition.HEADER),
116-
Field(name="fname", values=["fval1"], kind=FieldPosition.HEADER),
114+
Field(name="fname", values=["fval1", "fval2"], kind="header"),
115+
Field(name="fname", values=["fval1"], kind="header"),
117116
),
118117
(
119-
Field(name="fname1", values=["fval1", "fval2"], kind=FieldPosition.HEADER),
120-
Field(name="fname2", values=["fval1", "fval2"], kind=FieldPosition.HEADER),
118+
Field(name="fname1", values=["fval1", "fval2"], kind="header"),
119+
Field(name="fname2", values=["fval1", "fval2"], kind="header"),
121120
),
122121
],
123122
)
@@ -211,7 +210,7 @@ def test_fields_length_value(fields: Fields, expected_length: int) -> None:
211210
Fields([Field(name="fname1")]),
212211
(
213212
"Fields(OrderedDict({'fname1': Field(name='fname1', value=[], "
214-
"kind=<FieldPosition.HEADER: 0>)}))"
213+
"kind='header')}))"
215214
),
216215
),
217216
],
@@ -314,3 +313,8 @@ def test_fields_delitem_missing() -> None:
314313
fields = Fields([Field(name="fname1")])
315314
with pytest.raises(KeyError):
316315
del fields["fname2"]
316+
317+
318+
def test_field_invalid_kind() -> None:
319+
with pytest.raises(ValueError, match="Unknown field kind"):
320+
Field(name="fname", kind="metadata") # type: ignore
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"type": "breaking",
3+
"description": "Replace `FieldPosition` enum values with string literals for `Field.kind`. Use \"header\" and \"trailer\" instead of `FieldPosition.HEADER` and `FieldPosition.TRAILER`."
4+
}

packages/smithy-http/src/smithy_http/__init__.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,15 @@
22
# SPDX-License-Identifier: Apache-2.0
33
from collections import Counter, OrderedDict
44
from collections.abc import Iterable, Iterator
5+
from typing import get_args
56

67
from . import interfaces
78
from .interfaces import FieldPosition
89

910
__version__ = "0.3.1"
1011

12+
_VALID_FIELD_POSITIONS = frozenset(get_args(FieldPosition))
13+
1114

1215
class Field(interfaces.Field):
1316
"""A name-value pair representing a single field in an HTTP Request or Response.
@@ -24,10 +27,12 @@ def __init__(
2427
*,
2528
name: str,
2629
values: Iterable[str] | None = None,
27-
kind: FieldPosition = FieldPosition.HEADER,
30+
kind: FieldPosition = "header",
2831
):
2932
self.name = name
3033
self.values: list[str] = list(values) if values is not None else []
34+
if kind not in _VALID_FIELD_POSITIONS:
35+
raise ValueError(f"Unknown field kind: {kind!r}")
3136
self.kind = kind
3237

3338
def add(self, value: str) -> None:
@@ -79,7 +84,7 @@ def __eq__(self, other: object) -> bool:
7984
return False
8085
return (
8186
self.name == other.name
82-
and self.kind is other.kind
87+
and self.kind == other.kind
8388
and self.values == other.values
8489
)
8590

@@ -153,7 +158,7 @@ def get_by_type(self, kind: FieldPosition) -> list[interfaces.Field]:
153158
154159
Used to grab all headers or all trailers.
155160
"""
156-
return [entry for entry in self.entries.values() if entry.kind is kind]
161+
return [entry for entry in self.entries.values() if entry.kind == kind]
157162

158163
def extend(self, other: interfaces.Fields) -> None:
159164
"""Merges ``entries`` of ``other`` into the current ``entries``.
@@ -225,8 +230,6 @@ def tuples_to_fields(
225230
try:
226231
fields[name].add(value)
227232
except KeyError:
228-
fields[name] = Field(
229-
name=name, values=[value], kind=kind or FieldPosition.HEADER
230-
)
233+
fields[name] = Field(name=name, values=[value], kind=kind or "header")
231234

232235
return fields

packages/smithy-http/src/smithy_http/aio/aiohttp.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828

2929
from .. import Field, Fields
3030
from ..interfaces import (
31-
FieldPosition,
3231
HTTPClientConfiguration,
3332
HTTPRequestConfiguration,
3433
)
@@ -125,7 +124,7 @@ async def _marshal_response(
125124
headers[header_name] = Field(
126125
name=header_name,
127126
values=[header_val],
128-
kind=FieldPosition.HEADER,
127+
kind="header",
129128
)
130129

131130
return HTTPResponse(

packages/smithy-http/src/smithy_http/aio/crt.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@
4141
from .. import Field, Fields
4242
from .. import interfaces as http_interfaces
4343
from ..exceptions import SmithyHTTPError
44-
from ..interfaces import FieldPosition
4544
from . import interfaces as http_aio_interfaces
4645

4746
# Default buffer size for reading from streams (8 KB)
@@ -203,7 +202,7 @@ async def _await_response(
203202
fields[header_name] = Field(
204203
name=header_name,
205204
values=[header_val],
206-
kind=FieldPosition.HEADER,
205+
kind="header",
207206
)
208207
return AWSCRTHTTPResponse(
209208
status=status_code,
@@ -294,8 +293,7 @@ def _marshal_request(
294293
request.fields.set_field(Field(name="accept", values=["*/*"]))
295294

296295
for fld in request.fields.entries.values():
297-
# TODO: Use literal values for "header"/"trailer".
298-
if fld.kind.value != FieldPosition.HEADER.value:
296+
if fld.kind != "header":
299297
continue
300298
for val in fld.values:
301299
headers_list.append((fld.name, val))

packages/smithy-http/src/smithy_http/interfaces/__init__.py

Lines changed: 9 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,29 +2,18 @@
22
# SPDX-License-Identifier: Apache-2.0
33
from collections.abc import Iterator
44
from dataclasses import dataclass
5-
from enum import Enum
6-
from typing import Protocol
5+
from typing import Literal, Protocol
76

7+
FieldPosition = Literal["header", "trailer"]
8+
"""The type of a field.
89
9-
class FieldPosition(Enum):
10-
"""The type of a field.
10+
Defines its placement in a request or response.
1111
12-
Defines its placement in a request or response.
13-
"""
14-
15-
HEADER = 0
16-
"""Header field.
17-
18-
In HTTP this is a header as defined in RFC 9110 Section 6.3. Implementations of
19-
other protocols may use this FieldPosition for similar types of metadata.
20-
"""
21-
22-
TRAILER = 1
23-
"""Trailer field.
12+
header: Header field. In HTTP this is a header as defined in RFC 9110 Section 6.3.
13+
trailer: Trailer field. In HTTP this is a trailer as defined in RFC 9110 Section 6.5.
2414
25-
In HTTP this is a trailer as defined in RFC 9110 Section 6.5. Implementations of
26-
other protocols may use this FieldPosition for similar types of metadata.
27-
"""
15+
Implementations of other protocols may use this FieldPosition for similar types of metadata.
16+
"""
2817

2918

3019
class Field(Protocol):
@@ -40,7 +29,7 @@ class Field(Protocol):
4029

4130
name: str
4231
values: list[str]
43-
kind: FieldPosition = FieldPosition.HEADER
32+
kind: FieldPosition = "header"
4433

4534
def add(self, value: str) -> None:
4635
"""Append a value to a field."""

0 commit comments

Comments
 (0)