smithy-http 0.3.1__tar.gz → 0.4.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. smithy_http-0.4.1/.changes/0.4.0.json +12 -0
  2. smithy_http-0.4.1/.changes/0.4.1.json +8 -0
  3. {smithy_http-0.3.1 → smithy_http-0.4.1}/CHANGELOG.md +13 -0
  4. {smithy_http-0.3.1 → smithy_http-0.4.1}/PKG-INFO +3 -2
  5. {smithy_http-0.3.1 → smithy_http-0.4.1}/pyproject.toml +2 -1
  6. {smithy_http-0.3.1 → smithy_http-0.4.1}/src/smithy_http/__init__.py +10 -7
  7. {smithy_http-0.3.1 → smithy_http-0.4.1}/src/smithy_http/aio/aiohttp.py +1 -2
  8. {smithy_http-0.3.1 → smithy_http-0.4.1}/src/smithy_http/aio/crt.py +25 -11
  9. {smithy_http-0.3.1 → smithy_http-0.4.1}/src/smithy_http/interfaces/__init__.py +9 -20
  10. {smithy_http-0.3.1 → smithy_http-0.4.1}/.changes/0.0.1.json +0 -0
  11. {smithy_http-0.3.1 → smithy_http-0.4.1}/.changes/0.1.0.json +0 -0
  12. {smithy_http-0.3.1 → smithy_http-0.4.1}/.changes/0.2.0.json +0 -0
  13. {smithy_http-0.3.1 → smithy_http-0.4.1}/.changes/0.2.1.json +0 -0
  14. {smithy_http-0.3.1 → smithy_http-0.4.1}/.changes/0.3.0.json +0 -0
  15. {smithy_http-0.3.1 → smithy_http-0.4.1}/.changes/0.3.1.json +0 -0
  16. {smithy_http-0.3.1 → smithy_http-0.4.1}/.gitignore +0 -0
  17. {smithy_http-0.3.1 → smithy_http-0.4.1}/NOTICE +0 -0
  18. {smithy_http-0.3.1 → smithy_http-0.4.1}/README.md +0 -0
  19. {smithy_http-0.3.1 → smithy_http-0.4.1}/src/smithy_http/aio/__init__.py +0 -0
  20. {smithy_http-0.3.1 → smithy_http-0.4.1}/src/smithy_http/aio/auth/__init__.py +0 -0
  21. {smithy_http-0.3.1 → smithy_http-0.4.1}/src/smithy_http/aio/auth/apikey.py +0 -0
  22. {smithy_http-0.3.1 → smithy_http-0.4.1}/src/smithy_http/aio/identity/__init__.py +0 -0
  23. {smithy_http-0.3.1 → smithy_http-0.4.1}/src/smithy_http/aio/identity/apikey.py +0 -0
  24. {smithy_http-0.3.1 → smithy_http-0.4.1}/src/smithy_http/aio/interfaces/__init__.py +0 -0
  25. {smithy_http-0.3.1 → smithy_http-0.4.1}/src/smithy_http/aio/protocols.py +0 -0
  26. {smithy_http-0.3.1 → smithy_http-0.4.1}/src/smithy_http/aio/restjson.py +0 -0
  27. {smithy_http-0.3.1 → smithy_http-0.4.1}/src/smithy_http/bindings.py +0 -0
  28. {smithy_http-0.3.1 → smithy_http-0.4.1}/src/smithy_http/deserializers.py +0 -0
  29. {smithy_http-0.3.1 → smithy_http-0.4.1}/src/smithy_http/endpoints.py +0 -0
  30. {smithy_http-0.3.1 → smithy_http-0.4.1}/src/smithy_http/exceptions.py +0 -0
  31. {smithy_http-0.3.1 → smithy_http-0.4.1}/src/smithy_http/interceptors/__init__.py +0 -0
  32. {smithy_http-0.3.1 → smithy_http-0.4.1}/src/smithy_http/interceptors/user_agent.py +0 -0
  33. {smithy_http-0.3.1 → smithy_http-0.4.1}/src/smithy_http/plugins/__init__.py +0 -0
  34. {smithy_http-0.3.1 → smithy_http-0.4.1}/src/smithy_http/py.typed +0 -0
  35. {smithy_http-0.3.1 → smithy_http-0.4.1}/src/smithy_http/restjson.py +0 -0
  36. {smithy_http-0.3.1 → smithy_http-0.4.1}/src/smithy_http/serializers.py +0 -0
  37. {smithy_http-0.3.1 → smithy_http-0.4.1}/src/smithy_http/testing/__init__.py +0 -0
  38. {smithy_http-0.3.1 → smithy_http-0.4.1}/src/smithy_http/testing/mockhttp.py +0 -0
  39. {smithy_http-0.3.1 → smithy_http-0.4.1}/src/smithy_http/testing/utils.py +0 -0
  40. {smithy_http-0.3.1 → smithy_http-0.4.1}/src/smithy_http/user_agent.py +0 -0
  41. {smithy_http-0.3.1 → smithy_http-0.4.1}/src/smithy_http/utils.py +0 -0
@@ -0,0 +1,12 @@
1
+ {
2
+ "changes": [
3
+ {
4
+ "type": "breaking",
5
+ "description": "Replace `FieldPosition` enum values with string literals for `Field.kind`. Use \"header\" and \"trailer\" instead of `FieldPosition.HEADER` and `FieldPosition.TRAILER`."
6
+ },
7
+ {
8
+ "type": "dependency",
9
+ "description": "Upgrade CRT to 0.32.0 to fix bug where HTTP2 exceptions could hang indefinitely"
10
+ }
11
+ ]
12
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "changes": [
3
+ {
4
+ "type": "bugfix",
5
+ "description": "Fixed `AWSCRTHTTPClient` to send HTTP/1.1 request bodies via `body_stream` instead of the HTTP/2-only `request_body_generator`."
6
+ }
7
+ ]
8
+ }
@@ -1,5 +1,18 @@
1
1
  # Changelog
2
2
 
3
+ ## v0.4.1
4
+
5
+ ### Bug fixes
6
+ * Fixed `AWSCRTHTTPClient` to send HTTP/1.1 request bodies via `body_stream` instead of the HTTP/2-only `request_body_generator`.
7
+
8
+ ## v0.4.0
9
+
10
+ ### Breaking Changes
11
+ * Replace `FieldPosition` enum values with string literals for `Field.kind`. Use "header" and "trailer" instead of `FieldPosition.HEADER` and `FieldPosition.TRAILER`.
12
+
13
+ ### Dependencies
14
+ * Upgrade CRT to 0.32.0 to fix bug where HTTP2 exceptions could hang indefinitely
15
+
3
16
  ## v0.3.1
4
17
 
5
18
  ### Bug fixes
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: smithy-http
3
- Version: 0.3.1
3
+ Version: 0.4.1
4
4
  Summary: HTTP components for Smithy tooling.
5
5
  Project-URL: Changelog, https://github.com/smithy-lang/smithy-python/blob/develop/packages/smithy-http/CHANGELOG.md
6
6
  Project-URL: Code, https://github.com/smithy-lang/smithy-python/blob/develop/packages/smithy-http/
@@ -21,6 +21,7 @@ Classifier: Programming Language :: Python :: 3 :: Only
21
21
  Classifier: Programming Language :: Python :: 3.12
22
22
  Classifier: Programming Language :: Python :: 3.13
23
23
  Classifier: Programming Language :: Python :: 3.14
24
+ Classifier: Programming Language :: Python :: Free Threading :: 2 - Beta
24
25
  Classifier: Programming Language :: Python :: Implementation :: CPython
25
26
  Classifier: Topic :: Software Development :: Libraries
26
27
  Requires-Python: >=3.12
@@ -29,7 +30,7 @@ Provides-Extra: aiohttp
29
30
  Requires-Dist: aiohttp<4.0,>=3.11.12; extra == 'aiohttp'
30
31
  Requires-Dist: yarl; extra == 'aiohttp'
31
32
  Provides-Extra: awscrt
32
- Requires-Dist: awscrt~=0.28.2; extra == 'awscrt'
33
+ Requires-Dist: awscrt~=0.32.0; extra == 'awscrt'
33
34
  Description-Content-Type: text/markdown
34
35
 
35
36
  # smithy-http
@@ -23,6 +23,7 @@ classifiers = [
23
23
  "Programming Language :: Python :: 3.13",
24
24
  "Programming Language :: Python :: 3.14",
25
25
  "Programming Language :: Python :: Implementation :: CPython",
26
+ "Programming Language :: Python :: Free Threading :: 2 - Beta",
26
27
  "Topic :: Software Development :: Libraries"
27
28
  ]
28
29
  dependencies = [
@@ -36,7 +37,7 @@ dependencies = [
36
37
 
37
38
  [project.optional-dependencies]
38
39
  awscrt = [
39
- "awscrt~=0.28.2",
40
+ "awscrt~=0.32.0",
40
41
  ]
41
42
  aiohttp = [
42
43
  "aiohttp>=3.11.12, <4.0",
@@ -2,11 +2,14 @@
2
2
  # SPDX-License-Identifier: Apache-2.0
3
3
  from collections import Counter, OrderedDict
4
4
  from collections.abc import Iterable, Iterator
5
+ from typing import get_args
5
6
 
6
7
  from . import interfaces
7
8
  from .interfaces import FieldPosition
8
9
 
9
- __version__ = "0.3.1"
10
+ __version__ = "0.4.1"
11
+
12
+ _VALID_FIELD_POSITIONS = frozenset(get_args(FieldPosition))
10
13
 
11
14
 
12
15
  class Field(interfaces.Field):
@@ -24,10 +27,12 @@ class Field(interfaces.Field):
24
27
  *,
25
28
  name: str,
26
29
  values: Iterable[str] | None = None,
27
- kind: FieldPosition = FieldPosition.HEADER,
30
+ kind: FieldPosition = "header",
28
31
  ):
29
32
  self.name = name
30
33
  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}")
31
36
  self.kind = kind
32
37
 
33
38
  def add(self, value: str) -> None:
@@ -79,7 +84,7 @@ class Field(interfaces.Field):
79
84
  return False
80
85
  return (
81
86
  self.name == other.name
82
- and self.kind is other.kind
87
+ and self.kind == other.kind
83
88
  and self.values == other.values
84
89
  )
85
90
 
@@ -153,7 +158,7 @@ class Fields(interfaces.Fields):
153
158
 
154
159
  Used to grab all headers or all trailers.
155
160
  """
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]
157
162
 
158
163
  def extend(self, other: interfaces.Fields) -> None:
159
164
  """Merges ``entries`` of ``other`` into the current ``entries``.
@@ -225,8 +230,6 @@ def tuples_to_fields(
225
230
  try:
226
231
  fields[name].add(value)
227
232
  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")
231
234
 
232
235
  return fields
@@ -28,7 +28,6 @@ from smithy_core.interfaces import URI
28
28
 
29
29
  from .. import Field, Fields
30
30
  from ..interfaces import (
31
- FieldPosition,
32
31
  HTTPClientConfiguration,
33
32
  HTTPRequestConfiguration,
34
33
  )
@@ -125,7 +124,7 @@ class AIOHTTPClient(HTTPClient):
125
124
  headers[header_name] = Field(
126
125
  name=header_name,
127
126
  values=[header_val],
128
- kind=FieldPosition.HEADER,
127
+ kind="header",
129
128
  )
130
129
 
131
130
  return HTTPResponse(
@@ -6,6 +6,7 @@ from collections.abc import AsyncGenerator, AsyncIterable
6
6
  from copy import deepcopy
7
7
  from dataclasses import dataclass
8
8
  from inspect import iscoroutinefunction
9
+ from io import BytesIO
9
10
  from typing import TYPE_CHECKING, Any
10
11
 
11
12
  from awscrt.exceptions import AwsCrtError
@@ -36,12 +37,12 @@ except ImportError:
36
37
  from smithy_core import interfaces as core_interfaces
37
38
  from smithy_core.aio import interfaces as core_aio_interfaces
38
39
  from smithy_core.aio.types import AsyncBytesReader
40
+ from smithy_core.aio.utils import read_streaming_blob_async
39
41
  from smithy_core.exceptions import MissingDependencyError
40
42
 
41
43
  from .. import Field, Fields
42
44
  from .. import interfaces as http_interfaces
43
45
  from ..exceptions import SmithyHTTPError
44
- from ..interfaces import FieldPosition
45
46
  from . import interfaces as http_aio_interfaces
46
47
 
47
48
  # Default buffer size for reading from streams (8 KB)
@@ -176,13 +177,18 @@ class AWSCRTHTTPClient(http_aio_interfaces.HTTPClient):
176
177
  crt_request = self._marshal_request(request)
177
178
  connection = await self._get_connection(request.destination)
178
179
 
179
- # Convert body to async iterator for request_body_generator
180
- body_generator = self._create_body_generator(request.body)
181
-
182
- crt_stream = connection.request(
183
- crt_request,
184
- request_body_generator=body_generator,
185
- )
180
+ # request_body_generator is HTTP/2-only in CRT; HTTP/1.1 must use body_stream
181
+ if connection.version is crt_http.HttpVersion.Http2:
182
+ crt_stream = connection.request(
183
+ crt_request,
184
+ request_body_generator=self._create_body_generator(request.body),
185
+ )
186
+ else:
187
+ if (
188
+ body_stream := await self._create_body_stream(request.body)
189
+ ) is not None:
190
+ crt_request.body_stream = body_stream
191
+ crt_stream = connection.request(crt_request)
186
192
 
187
193
  return await self._await_response(crt_stream)
188
194
  except AwsCrtError as e:
@@ -203,7 +209,7 @@ class AWSCRTHTTPClient(http_aio_interfaces.HTTPClient):
203
209
  fields[header_name] = Field(
204
210
  name=header_name,
205
211
  values=[header_val],
206
- kind=FieldPosition.HEADER,
212
+ kind="header",
207
213
  )
208
214
  return AWSCRTHTTPResponse(
209
215
  status=status_code,
@@ -294,8 +300,7 @@ class AWSCRTHTTPClient(http_aio_interfaces.HTTPClient):
294
300
  request.fields.set_field(Field(name="accept", values=["*/*"]))
295
301
 
296
302
  for fld in request.fields.entries.values():
297
- # TODO: Use literal values for "header"/"trailer".
298
- if fld.kind.value != FieldPosition.HEADER.value:
303
+ if fld.kind != "header":
299
304
  continue
300
305
  for val in fld.values:
301
306
  headers_list.append((fld.name, val))
@@ -310,6 +315,15 @@ class AWSCRTHTTPClient(http_aio_interfaces.HTTPClient):
310
315
  )
311
316
  return crt_request
312
317
 
318
+ async def _create_body_stream(
319
+ self, body: core_aio_interfaces.StreamingBlob
320
+ ) -> core_interfaces.BytesReader | None:
321
+ """Convert various body types to a bytes reader for CRT HTTP/1.1."""
322
+ if core_interfaces.is_bytes_reader(body):
323
+ return body
324
+ buffered = await read_streaming_blob_async(body)
325
+ return BytesIO(buffered) if buffered else None
326
+
313
327
  async def _create_body_generator(
314
328
  self, body: core_aio_interfaces.StreamingBlob
315
329
  ) -> AsyncGenerator[bytes, None]:
@@ -2,29 +2,18 @@
2
2
  # SPDX-License-Identifier: Apache-2.0
3
3
  from collections.abc import Iterator
4
4
  from dataclasses import dataclass
5
- from enum import Enum
6
- from typing import Protocol
5
+ from typing import Literal, Protocol
7
6
 
7
+ FieldPosition = Literal["header", "trailer"]
8
+ """The type of a field.
8
9
 
9
- class FieldPosition(Enum):
10
- """The type of a field.
10
+ Defines its placement in a request or response.
11
11
 
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.
24
14
 
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
+ """
28
17
 
29
18
 
30
19
  class Field(Protocol):
@@ -40,7 +29,7 @@ class Field(Protocol):
40
29
 
41
30
  name: str
42
31
  values: list[str]
43
- kind: FieldPosition = FieldPosition.HEADER
32
+ kind: FieldPosition = "header"
44
33
 
45
34
  def add(self, value: str) -> None:
46
35
  """Append a value to a field."""
File without changes
File without changes
File without changes