smithy-http 0.4.2__tar.gz → 0.4.4__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 (44) hide show
  1. smithy_http-0.4.4/.changes/0.4.3.json +12 -0
  2. smithy_http-0.4.4/.changes/0.4.4.json +16 -0
  3. {smithy_http-0.4.2 → smithy_http-0.4.4}/CHANGELOG.md +15 -0
  4. {smithy_http-0.4.2 → smithy_http-0.4.4}/PKG-INFO +2 -2
  5. {smithy_http-0.4.2 → smithy_http-0.4.4}/src/smithy_http/__init__.py +1 -1
  6. {smithy_http-0.4.2 → smithy_http-0.4.4}/src/smithy_http/aio/aiohttp.py +25 -2
  7. {smithy_http-0.4.2 → smithy_http-0.4.4}/src/smithy_http/aio/crt.py +4 -1
  8. {smithy_http-0.4.2 → smithy_http-0.4.4}/src/smithy_http/aio/protocols.py +34 -3
  9. {smithy_http-0.4.2 → smithy_http-0.4.4}/src/smithy_http/testing/mockhttp.py +5 -0
  10. {smithy_http-0.4.2 → smithy_http-0.4.4}/.changes/0.0.1.json +0 -0
  11. {smithy_http-0.4.2 → smithy_http-0.4.4}/.changes/0.1.0.json +0 -0
  12. {smithy_http-0.4.2 → smithy_http-0.4.4}/.changes/0.2.0.json +0 -0
  13. {smithy_http-0.4.2 → smithy_http-0.4.4}/.changes/0.2.1.json +0 -0
  14. {smithy_http-0.4.2 → smithy_http-0.4.4}/.changes/0.3.0.json +0 -0
  15. {smithy_http-0.4.2 → smithy_http-0.4.4}/.changes/0.3.1.json +0 -0
  16. {smithy_http-0.4.2 → smithy_http-0.4.4}/.changes/0.4.0.json +0 -0
  17. {smithy_http-0.4.2 → smithy_http-0.4.4}/.changes/0.4.1.json +0 -0
  18. {smithy_http-0.4.2 → smithy_http-0.4.4}/.changes/0.4.2.json +0 -0
  19. {smithy_http-0.4.2 → smithy_http-0.4.4}/.gitignore +0 -0
  20. {smithy_http-0.4.2 → smithy_http-0.4.4}/NOTICE +0 -0
  21. {smithy_http-0.4.2 → smithy_http-0.4.4}/README.md +0 -0
  22. {smithy_http-0.4.2 → smithy_http-0.4.4}/pyproject.toml +0 -0
  23. {smithy_http-0.4.2 → smithy_http-0.4.4}/src/smithy_http/aio/__init__.py +0 -0
  24. {smithy_http-0.4.2 → smithy_http-0.4.4}/src/smithy_http/aio/auth/__init__.py +0 -0
  25. {smithy_http-0.4.2 → smithy_http-0.4.4}/src/smithy_http/aio/auth/apikey.py +0 -0
  26. {smithy_http-0.4.2 → smithy_http-0.4.4}/src/smithy_http/aio/identity/__init__.py +0 -0
  27. {smithy_http-0.4.2 → smithy_http-0.4.4}/src/smithy_http/aio/identity/apikey.py +0 -0
  28. {smithy_http-0.4.2 → smithy_http-0.4.4}/src/smithy_http/aio/interfaces/__init__.py +0 -0
  29. {smithy_http-0.4.2 → smithy_http-0.4.4}/src/smithy_http/aio/restjson.py +0 -0
  30. {smithy_http-0.4.2 → smithy_http-0.4.4}/src/smithy_http/bindings.py +0 -0
  31. {smithy_http-0.4.2 → smithy_http-0.4.4}/src/smithy_http/deserializers.py +0 -0
  32. {smithy_http-0.4.2 → smithy_http-0.4.4}/src/smithy_http/endpoints.py +0 -0
  33. {smithy_http-0.4.2 → smithy_http-0.4.4}/src/smithy_http/exceptions.py +0 -0
  34. {smithy_http-0.4.2 → smithy_http-0.4.4}/src/smithy_http/interceptors/__init__.py +0 -0
  35. {smithy_http-0.4.2 → smithy_http-0.4.4}/src/smithy_http/interceptors/user_agent.py +0 -0
  36. {smithy_http-0.4.2 → smithy_http-0.4.4}/src/smithy_http/interfaces/__init__.py +0 -0
  37. {smithy_http-0.4.2 → smithy_http-0.4.4}/src/smithy_http/plugins/__init__.py +0 -0
  38. {smithy_http-0.4.2 → smithy_http-0.4.4}/src/smithy_http/py.typed +0 -0
  39. {smithy_http-0.4.2 → smithy_http-0.4.4}/src/smithy_http/restjson.py +0 -0
  40. {smithy_http-0.4.2 → smithy_http-0.4.4}/src/smithy_http/serializers.py +0 -0
  41. {smithy_http-0.4.2 → smithy_http-0.4.4}/src/smithy_http/testing/__init__.py +0 -0
  42. {smithy_http-0.4.2 → smithy_http-0.4.4}/src/smithy_http/testing/utils.py +0 -0
  43. {smithy_http-0.4.2 → smithy_http-0.4.4}/src/smithy_http/user_agent.py +0 -0
  44. {smithy_http-0.4.2 → smithy_http-0.4.4}/src/smithy_http/utils.py +0 -0
@@ -0,0 +1,12 @@
1
+ {
2
+ "changes": [
3
+ {
4
+ "type": "enhancement",
5
+ "description": "Enabled HTTP binding protocols to resolve modeled response errors when wire identifiers do not exactly match registered shape IDs. ([#742](https://github.com/smithy-lang/smithy-python/pull/742))"
6
+ },
7
+ {
8
+ "type": "bugfix",
9
+ "description": "Fixed `AIOHTTPClient` sending omitted request payloads with chunked transfer encoding."
10
+ }
11
+ ]
12
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "changes": [
3
+ {
4
+ "type": "enhancement",
5
+ "description": "Added a protocol hook for extracting server-provided retry delays from error responses."
6
+ },
7
+ {
8
+ "type": "enhancement",
9
+ "description": "Declared `AWSCRTHTTPClient` as duplex (bidirectional) streaming capable via `SUPPORTS_DUPLEX_STREAMING`. `AIOHTTPClient` explicitly does not support duplex streaming."
10
+ },
11
+ {
12
+ "type": "enhancement",
13
+ "description": "Disabled automatic redirects in `AIOHTTPClient` so that redirect responses are returned to the caller instead of being followed transparently."
14
+ }
15
+ ]
16
+ }
@@ -1,5 +1,20 @@
1
1
  # Changelog
2
2
 
3
+ ## v0.4.4
4
+
5
+ ### Enhancements
6
+ * Added a protocol hook for extracting server-provided retry delays from error responses.
7
+ * Declared `AWSCRTHTTPClient` as duplex (bidirectional) streaming capable via `SUPPORTS_DUPLEX_STREAMING`. `AIOHTTPClient` explicitly does not support duplex streaming.
8
+ * Disabled automatic redirects in `AIOHTTPClient` so that redirect responses are returned to the caller instead of being followed transparently.
9
+
10
+ ## v0.4.3
11
+
12
+ ### Enhancements
13
+ * Enabled HTTP binding protocols to resolve modeled response errors when wire identifiers do not exactly match registered shape IDs. ([#742](https://github.com/smithy-lang/smithy-python/pull/742))
14
+
15
+ ### Bug fixes
16
+ * Fixed `AIOHTTPClient` sending omitted request payloads with chunked transfer encoding.
17
+
3
18
  ## v0.4.2
4
19
 
5
20
  ### Bug fixes
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: smithy-http
3
- Version: 0.4.2
3
+ Version: 0.4.4
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/
@@ -7,7 +7,7 @@ from typing import get_args
7
7
  from . import interfaces
8
8
  from .interfaces import FieldPosition
9
9
 
10
- __version__ = "0.4.2"
10
+ __version__ = "0.4.4"
11
11
 
12
12
  _VALID_FIELD_POSITIONS = frozenset(get_args(FieldPosition))
13
13
 
@@ -53,6 +53,10 @@ class AIOHTTPClient(HTTPClient):
53
53
 
54
54
  TIMEOUT_EXCEPTIONS = (TimeoutError,)
55
55
 
56
+ # aiohttp has no HTTP/2 support and this client fully buffers the response
57
+ # before returning, so it can never interleave request and response data.
58
+ SUPPORTS_DUPLEX_STREAMING = False
59
+
56
60
  def __init__(
57
61
  self,
58
62
  *,
@@ -90,8 +94,13 @@ class AIOHTTPClient(HTTPClient):
90
94
  chain.from_iterable(fld.as_tuples() for fld in request.fields)
91
95
  )
92
96
 
93
- body: StreamingBlob = request.body
94
- if not isinstance(body, AsyncBytesReader):
97
+ body: StreamingBlob | None = request.body
98
+ if (
99
+ "content-length" not in request.fields
100
+ and "transfer-encoding" not in request.fields
101
+ ):
102
+ body = await self._prepare_body(body)
103
+ elif not isinstance(body, AsyncBytesReader):
95
104
  body = AsyncBytesReader(body)
96
105
 
97
106
  # The typing on `params` is incorrect, it'll happily accept a mapping whose
@@ -103,9 +112,23 @@ class AIOHTTPClient(HTTPClient):
103
112
  params=parse_qs(request.destination.query), # type: ignore
104
113
  headers=headers_list,
105
114
  data=body,
115
+ allow_redirects=False,
106
116
  ) as resp:
107
117
  return await self._marshal_response(resp)
108
118
 
119
+ async def _prepare_body(self, body: StreamingBlob) -> AsyncBytesReader | None:
120
+ """Convert a body for aiohttp, omitting seekable bodies with no data."""
121
+ if not isinstance(body, AsyncBytesReader):
122
+ body = AsyncBytesReader(body)
123
+
124
+ if not body.seekable():
125
+ return body
126
+
127
+ position = await body.seek(0, 1)
128
+ end = await body.seek(0, 2)
129
+ await body.seek(position)
130
+ return None if position == end else body
131
+
109
132
  def _serialize_uri_without_query(self, uri: URI) -> yarl.URL:
110
133
  """Serialize all parts of the URI up to and including the path."""
111
134
  return yarl.URL.build(
@@ -1,7 +1,6 @@
1
1
  # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
2
  # SPDX-License-Identifier: Apache-2.0
3
3
  # pyright: reportMissingTypeStubs=false,reportUnknownMemberType=false
4
- # flake8: noqa: F811
5
4
  from collections.abc import AsyncGenerator, AsyncIterable
6
5
  from copy import deepcopy
7
6
  from dataclasses import dataclass
@@ -143,6 +142,10 @@ class AWSCRTHTTPClient(http_aio_interfaces.HTTPClient):
143
142
 
144
143
  TIMEOUT_EXCEPTIONS = (_CRTTimeoutError,)
145
144
 
145
+ # CRT can stream request and response data concurrently when the connection
146
+ # negotiates HTTP/2.
147
+ SUPPORTS_DUPLEX_STREAMING = True
148
+
146
149
  def __init__(
147
150
  self,
148
151
  eventloop: _AWSCRTEventLoop | None = None,
@@ -23,6 +23,7 @@ from smithy_core.interfaces import StreamingBlob as SyncStreamingBlob
23
23
  from smithy_core.prelude import DOCUMENT
24
24
  from smithy_core.schemas import APIOperation
25
25
  from smithy_core.serializers import SerializeableShape
26
+ from smithy_core.shapes import ShapeID
26
27
  from smithy_core.traits import EndpointTrait, HTTPTrait
27
28
 
28
29
  from ..deserializers import HTTPResponseDeserializer
@@ -185,15 +186,28 @@ class HttpBindingClientProtocol(HttpClientProtocol):
185
186
  error_id = self.error_identifier.identify(
186
187
  operation=operation, response=response
187
188
  )
189
+ if error_id is not None and error_id not in error_registry:
190
+ error_id = self._resolve_error_id(
191
+ operation=operation,
192
+ error_id=error_id,
193
+ )
194
+
195
+ retry_after = self._retry_after(response)
188
196
 
189
197
  if error_id is None and self._matches_content_type(response):
190
198
  if isinstance(response_body, bytearray):
191
199
  response_body = bytes(response_body)
192
200
  deserializer = self.payload_codec.create_deserializer(source=response_body)
193
201
  document = deserializer.read_document(schema=DOCUMENT)
202
+ document_error_id = document.discriminator
203
+ if document_error_id not in error_registry:
204
+ document_error_id = self._resolve_error_id(
205
+ operation=operation,
206
+ error_id=document_error_id,
207
+ )
194
208
 
195
- if document.discriminator in error_registry:
196
- error_id = document.discriminator
209
+ if document_error_id in error_registry:
210
+ error_id = document_error_id
197
211
  if isinstance(response_body, SeekableBytesReader):
198
212
  response_body.seek(0)
199
213
 
@@ -213,7 +227,10 @@ class HttpBindingClientProtocol(HttpClientProtocol):
213
227
  response=response,
214
228
  body=response_body,
215
229
  )
216
- return error_shape.deserialize(deserializer)
230
+ modeled_error = error_shape.deserialize(deserializer)
231
+ if retry_after is not None:
232
+ modeled_error.retry_after = retry_after
233
+ return modeled_error
217
234
 
218
235
  message = (
219
236
  f"Unknown error for operation {operation.schema.id} "
@@ -234,8 +251,22 @@ class HttpBindingClientProtocol(HttpClientProtocol):
234
251
  is_throttling_error=is_throttle,
235
252
  is_timeout_error=is_timeout,
236
253
  is_retry_safe=is_throttle or is_timeout or None,
254
+ retry_after=retry_after,
237
255
  )
238
256
 
257
+ def _retry_after(self, response: HTTPResponse) -> float | None:
258
+ """The retry delay in seconds requested by the server, if the response carries one."""
259
+ return None
260
+
261
+ def _resolve_error_id(
262
+ self,
263
+ *,
264
+ operation: APIOperation[Any, Any],
265
+ error_id: ShapeID,
266
+ ) -> ShapeID:
267
+ """Resolve a response discriminator to its modeled error shape ID."""
268
+ return error_id
269
+
239
270
  def _matches_content_type(self, response: HTTPResponse) -> bool:
240
271
  if "content-type" not in response.fields:
241
272
  return False
@@ -22,6 +22,11 @@ class MockHTTPClient(HTTPClient):
22
22
 
23
23
  TIMEOUT_EXCEPTIONS = (TimeoutError,)
24
24
 
25
+ # Declared so duplex (bidirectional) stream operations can be unit tested
26
+ # against this client. Queued responses are returned without consuming the
27
+ # request body, which is compatible with duplex streaming.
28
+ SUPPORTS_DUPLEX_STREAMING = True
29
+
25
30
  def __init__(
26
31
  self,
27
32
  *,
File without changes
File without changes
File without changes
File without changes