smithy-http 0.4.4__tar.gz → 0.6.0__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.
- smithy_http-0.6.0/.changes/0.5.0.json +20 -0
- smithy_http-0.6.0/.changes/0.6.0.json +20 -0
- {smithy_http-0.4.4 → smithy_http-0.6.0}/CHANGELOG.md +20 -0
- {smithy_http-0.4.4 → smithy_http-0.6.0}/PKG-INFO +3 -1
- {smithy_http-0.4.4 → smithy_http-0.6.0}/pyproject.toml +3 -0
- {smithy_http-0.4.4 → smithy_http-0.6.0}/src/smithy_http/__init__.py +13 -5
- {smithy_http-0.4.4 → smithy_http-0.6.0}/src/smithy_http/aio/aiohttp.py +55 -17
- {smithy_http-0.4.4 → smithy_http-0.6.0}/src/smithy_http/aio/crt.py +28 -1
- smithy_http-0.6.0/src/smithy_http/aio/protocols.py +518 -0
- {smithy_http-0.4.4 → smithy_http-0.6.0}/src/smithy_http/bindings.py +4 -3
- {smithy_http-0.4.4 → smithy_http-0.6.0}/src/smithy_http/deserializers.py +193 -57
- smithy_http-0.6.0/src/smithy_http/schema_extensions.py +490 -0
- smithy_http-0.6.0/src/smithy_http/serializers.py +1050 -0
- smithy_http-0.4.4/src/smithy_http/aio/protocols.py +0 -273
- smithy_http-0.4.4/src/smithy_http/serializers.py +0 -781
- {smithy_http-0.4.4 → smithy_http-0.6.0}/.changes/0.0.1.json +0 -0
- {smithy_http-0.4.4 → smithy_http-0.6.0}/.changes/0.1.0.json +0 -0
- {smithy_http-0.4.4 → smithy_http-0.6.0}/.changes/0.2.0.json +0 -0
- {smithy_http-0.4.4 → smithy_http-0.6.0}/.changes/0.2.1.json +0 -0
- {smithy_http-0.4.4 → smithy_http-0.6.0}/.changes/0.3.0.json +0 -0
- {smithy_http-0.4.4 → smithy_http-0.6.0}/.changes/0.3.1.json +0 -0
- {smithy_http-0.4.4 → smithy_http-0.6.0}/.changes/0.4.0.json +0 -0
- {smithy_http-0.4.4 → smithy_http-0.6.0}/.changes/0.4.1.json +0 -0
- {smithy_http-0.4.4 → smithy_http-0.6.0}/.changes/0.4.2.json +0 -0
- {smithy_http-0.4.4 → smithy_http-0.6.0}/.changes/0.4.3.json +0 -0
- {smithy_http-0.4.4 → smithy_http-0.6.0}/.changes/0.4.4.json +0 -0
- {smithy_http-0.4.4 → smithy_http-0.6.0}/.gitignore +0 -0
- {smithy_http-0.4.4 → smithy_http-0.6.0}/NOTICE +0 -0
- {smithy_http-0.4.4 → smithy_http-0.6.0}/README.md +0 -0
- {smithy_http-0.4.4 → smithy_http-0.6.0}/src/smithy_http/aio/__init__.py +0 -0
- {smithy_http-0.4.4 → smithy_http-0.6.0}/src/smithy_http/aio/auth/__init__.py +0 -0
- {smithy_http-0.4.4 → smithy_http-0.6.0}/src/smithy_http/aio/auth/apikey.py +0 -0
- {smithy_http-0.4.4 → smithy_http-0.6.0}/src/smithy_http/aio/identity/__init__.py +0 -0
- {smithy_http-0.4.4 → smithy_http-0.6.0}/src/smithy_http/aio/identity/apikey.py +0 -0
- {smithy_http-0.4.4 → smithy_http-0.6.0}/src/smithy_http/aio/interfaces/__init__.py +0 -0
- {smithy_http-0.4.4 → smithy_http-0.6.0}/src/smithy_http/aio/restjson.py +0 -0
- {smithy_http-0.4.4 → smithy_http-0.6.0}/src/smithy_http/endpoints.py +0 -0
- {smithy_http-0.4.4 → smithy_http-0.6.0}/src/smithy_http/exceptions.py +0 -0
- {smithy_http-0.4.4 → smithy_http-0.6.0}/src/smithy_http/interceptors/__init__.py +0 -0
- {smithy_http-0.4.4 → smithy_http-0.6.0}/src/smithy_http/interceptors/user_agent.py +0 -0
- {smithy_http-0.4.4 → smithy_http-0.6.0}/src/smithy_http/interfaces/__init__.py +0 -0
- {smithy_http-0.4.4 → smithy_http-0.6.0}/src/smithy_http/plugins/__init__.py +0 -0
- {smithy_http-0.4.4 → smithy_http-0.6.0}/src/smithy_http/py.typed +0 -0
- {smithy_http-0.4.4 → smithy_http-0.6.0}/src/smithy_http/restjson.py +0 -0
- {smithy_http-0.4.4 → smithy_http-0.6.0}/src/smithy_http/testing/__init__.py +0 -0
- {smithy_http-0.4.4 → smithy_http-0.6.0}/src/smithy_http/testing/mockhttp.py +0 -0
- {smithy_http-0.4.4 → smithy_http-0.6.0}/src/smithy_http/testing/utils.py +0 -0
- {smithy_http-0.4.4 → smithy_http-0.6.0}/src/smithy_http/user_agent.py +0 -0
- {smithy_http-0.4.4 → smithy_http-0.6.0}/src/smithy_http/utils.py +0 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"changes": [
|
|
3
|
+
{
|
|
4
|
+
"type": "feature",
|
|
5
|
+
"description": "Added deterministic connection-pool cleanup and async context manager support to HTTP clients."
|
|
6
|
+
},
|
|
7
|
+
{
|
|
8
|
+
"type": "bugfix",
|
|
9
|
+
"description": "Stream aiohttp response bodies incrementally to enable true output event streams."
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"type": "bugfix",
|
|
13
|
+
"description": "Fixed `AIOHTTPClient` to stop dropping valueless query parameters and automatically adding a `Content-Type` to bodyless POST, PUT, and PATCH requests."
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"type": "bugfix",
|
|
17
|
+
"description": "Fixed duplicate Content-Type headers when a member is bound to Content-Type."
|
|
18
|
+
}
|
|
19
|
+
]
|
|
20
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"changes": [
|
|
3
|
+
{
|
|
4
|
+
"type": "feature",
|
|
5
|
+
"description": "`HttpClientProtocol` implements `extract_response_metadata()`, reporting the HTTP status code."
|
|
6
|
+
},
|
|
7
|
+
{
|
|
8
|
+
"type": "feature",
|
|
9
|
+
"description": "Added a naive `RpcV2CborClientProtocol` for the `smithy.protocols#rpcv2Cbor` protocol (optional `cbor` extra)."
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"type": "feature",
|
|
13
|
+
"description": "Added schema-cached HTTP binding plans for members, operations, payloads, and request and response dispatch."
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"type": "dependency",
|
|
17
|
+
"description": "Bump `smithy-cbor` from `~=0.0.0` to `~=0.1.0`."
|
|
18
|
+
}
|
|
19
|
+
]
|
|
20
|
+
}
|
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v0.6.0
|
|
4
|
+
|
|
5
|
+
### Features
|
|
6
|
+
* `HttpClientProtocol` implements `extract_response_metadata()`, reporting the HTTP status code.
|
|
7
|
+
* Added a naive `RpcV2CborClientProtocol` for the `smithy.protocols#rpcv2Cbor` protocol (optional `cbor` extra).
|
|
8
|
+
* Added schema-cached HTTP binding plans for members, operations, payloads, and request and response dispatch.
|
|
9
|
+
|
|
10
|
+
### Dependencies
|
|
11
|
+
* Bump `smithy-cbor` from `~=0.0.0` to `~=0.1.0`.
|
|
12
|
+
|
|
13
|
+
## v0.5.0
|
|
14
|
+
|
|
15
|
+
### Features
|
|
16
|
+
* Added deterministic connection-pool cleanup and async context manager support to HTTP clients.
|
|
17
|
+
|
|
18
|
+
### Bug fixes
|
|
19
|
+
* Stream aiohttp response bodies incrementally to enable true output event streams.
|
|
20
|
+
* Fixed `AIOHTTPClient` to stop dropping valueless query parameters and automatically adding a `Content-Type` to bodyless POST, PUT, and PATCH requests.
|
|
21
|
+
* Fixed duplicate Content-Type headers when a member is bound to Content-Type.
|
|
22
|
+
|
|
3
23
|
## v0.4.4
|
|
4
24
|
|
|
5
25
|
### Enhancements
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: smithy-http
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.0
|
|
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/
|
|
@@ -31,6 +31,8 @@ Requires-Dist: aiohttp<4.0,>=3.11.12; extra == 'aiohttp'
|
|
|
31
31
|
Requires-Dist: yarl; extra == 'aiohttp'
|
|
32
32
|
Provides-Extra: awscrt
|
|
33
33
|
Requires-Dist: awscrt~=0.32.0; extra == 'awscrt'
|
|
34
|
+
Provides-Extra: cbor
|
|
35
|
+
Requires-Dist: smithy-cbor~=0.1.0; extra == 'cbor'
|
|
34
36
|
Description-Content-Type: text/markdown
|
|
35
37
|
|
|
36
38
|
# 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.
|
|
10
|
+
__version__ = "0.6.0"
|
|
11
11
|
|
|
12
12
|
_VALID_FIELD_POSITIONS = frozenset(get_args(FieldPosition))
|
|
13
13
|
|
|
@@ -226,10 +226,18 @@ def tuples_to_fields(
|
|
|
226
226
|
:param kind: The Field kind to define for all tuples.
|
|
227
227
|
"""
|
|
228
228
|
fields = Fields()
|
|
229
|
+
entries = fields.entries
|
|
230
|
+
field_kind = kind or "header"
|
|
229
231
|
for name, value in tuples:
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
232
|
+
normalized_name = name.lower()
|
|
233
|
+
field = entries.get(normalized_name)
|
|
234
|
+
if field is None:
|
|
235
|
+
entries[normalized_name] = Field(
|
|
236
|
+
name=name,
|
|
237
|
+
values=[value],
|
|
238
|
+
kind=field_kind,
|
|
239
|
+
)
|
|
240
|
+
else:
|
|
241
|
+
field.values.append(value)
|
|
234
242
|
|
|
235
243
|
return fields
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
2
|
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
from collections.abc import AsyncIterable, AsyncIterator
|
|
3
4
|
from copy import copy, deepcopy
|
|
4
5
|
from itertools import chain
|
|
5
|
-
from typing import TYPE_CHECKING, Any
|
|
6
|
-
from urllib.parse import parse_qs
|
|
6
|
+
from typing import TYPE_CHECKING, Any, Self
|
|
7
7
|
|
|
8
8
|
import yarl
|
|
9
9
|
|
|
@@ -22,11 +22,11 @@ except ImportError:
|
|
|
22
22
|
|
|
23
23
|
from smithy_core.aio.interfaces import StreamingBlob
|
|
24
24
|
from smithy_core.aio.types import AsyncBytesReader
|
|
25
|
-
from smithy_core.aio.utils import async_list
|
|
26
25
|
from smithy_core.exceptions import MissingDependencyError
|
|
27
26
|
from smithy_core.interfaces import URI
|
|
28
27
|
|
|
29
28
|
from .. import Field, Fields
|
|
29
|
+
from ..exceptions import SmithyHTTPError
|
|
30
30
|
from ..interfaces import (
|
|
31
31
|
HTTPClientConfiguration,
|
|
32
32
|
HTTPRequestConfiguration,
|
|
@@ -48,13 +48,28 @@ class AIOHTTPClientConfig(HTTPClientConfiguration):
|
|
|
48
48
|
_assert_aiohttp()
|
|
49
49
|
|
|
50
50
|
|
|
51
|
+
class _AIOHTTPStreamingBody(AsyncIterable[bytes]):
|
|
52
|
+
"""Streams a response body, releasing the response once it is done."""
|
|
53
|
+
|
|
54
|
+
def __init__(self, response: "aiohttp.ClientResponse") -> None:
|
|
55
|
+
self._response = response
|
|
56
|
+
|
|
57
|
+
def __aiter__(self) -> AsyncIterator[bytes]:
|
|
58
|
+
# The reader iterates by line, so use iter_any to get raw chunks.
|
|
59
|
+
return self._response.content.iter_any()
|
|
60
|
+
|
|
61
|
+
async def close(self) -> None:
|
|
62
|
+
# Pools the connection if the body was read to completion, closes it if not.
|
|
63
|
+
self._response.release()
|
|
64
|
+
|
|
65
|
+
|
|
51
66
|
class AIOHTTPClient(HTTPClient):
|
|
52
67
|
"""Implementation of :py:class:`.interfaces.HTTPClient` using aiohttp."""
|
|
53
68
|
|
|
54
69
|
TIMEOUT_EXCEPTIONS = (TimeoutError,)
|
|
55
70
|
|
|
56
|
-
# aiohttp has no HTTP/2 support
|
|
57
|
-
#
|
|
71
|
+
# aiohttp has no HTTP/2 support, so it can never interleave request and
|
|
72
|
+
# response data.
|
|
58
73
|
SUPPORTS_DUPLEX_STREAMING = False
|
|
59
74
|
|
|
60
75
|
def __init__(
|
|
@@ -69,6 +84,7 @@ class AIOHTTPClient(HTTPClient):
|
|
|
69
84
|
"""
|
|
70
85
|
_assert_aiohttp()
|
|
71
86
|
self._config = client_config or AIOHTTPClientConfig()
|
|
87
|
+
self._closed = False
|
|
72
88
|
# Disable transparent response decompression and advertise
|
|
73
89
|
# 'identity' to request uncompressed responses.
|
|
74
90
|
# TODO: add a functional test once the test client framework exists
|
|
@@ -88,6 +104,11 @@ class AIOHTTPClient(HTTPClient):
|
|
|
88
104
|
:param request: The request including destination URI, fields, payload.
|
|
89
105
|
:param request_config: Configuration specific to this request.
|
|
90
106
|
"""
|
|
107
|
+
if self._closed:
|
|
108
|
+
raise SmithyHTTPError(
|
|
109
|
+
"Cannot send a request after the HTTP client has been closed."
|
|
110
|
+
)
|
|
111
|
+
|
|
91
112
|
request_config = request_config or HTTPRequestConfiguration()
|
|
92
113
|
|
|
93
114
|
headers_list = list(
|
|
@@ -103,18 +124,34 @@ class AIOHTTPClient(HTTPClient):
|
|
|
103
124
|
elif not isinstance(body, AsyncBytesReader):
|
|
104
125
|
body = AsyncBytesReader(body)
|
|
105
126
|
|
|
106
|
-
|
|
107
|
-
# values are lists (or tuples) and produce expected values.
|
|
108
|
-
# See: https://github.com/aio-libs/aiohttp/issues/8563
|
|
109
|
-
async with self._session.request(
|
|
127
|
+
resp = await self._session.request(
|
|
110
128
|
method=request.method,
|
|
111
|
-
url=self.
|
|
112
|
-
params=parse_qs(request.destination.query), # type: ignore
|
|
129
|
+
url=self._serialize_uri(request.destination),
|
|
113
130
|
headers=headers_list,
|
|
114
131
|
data=body,
|
|
115
132
|
allow_redirects=False,
|
|
116
|
-
|
|
117
|
-
|
|
133
|
+
skip_auto_headers={"Content-Type"},
|
|
134
|
+
)
|
|
135
|
+
try:
|
|
136
|
+
return self._marshal_response(resp)
|
|
137
|
+
except BaseException:
|
|
138
|
+
resp.release()
|
|
139
|
+
raise
|
|
140
|
+
|
|
141
|
+
async def close(self) -> None:
|
|
142
|
+
"""Close the underlying aiohttp session and its connection pool."""
|
|
143
|
+
if self._closed:
|
|
144
|
+
return
|
|
145
|
+
self._closed = True
|
|
146
|
+
await self._session.close()
|
|
147
|
+
|
|
148
|
+
async def __aenter__(self) -> Self:
|
|
149
|
+
if self._closed:
|
|
150
|
+
raise SmithyHTTPError("Cannot enter an HTTP client that has been closed.")
|
|
151
|
+
return self
|
|
152
|
+
|
|
153
|
+
async def __aexit__(self, exc_type: Any, exc_value: Any, traceback: Any) -> None:
|
|
154
|
+
await self.close()
|
|
118
155
|
|
|
119
156
|
async def _prepare_body(self, body: StreamingBlob) -> AsyncBytesReader | None:
|
|
120
157
|
"""Convert a body for aiohttp, omitting seekable bodies with no data."""
|
|
@@ -129,8 +166,8 @@ class AIOHTTPClient(HTTPClient):
|
|
|
129
166
|
await body.seek(position)
|
|
130
167
|
return None if position == end else body
|
|
131
168
|
|
|
132
|
-
def
|
|
133
|
-
"""Serialize
|
|
169
|
+
def _serialize_uri(self, uri: URI) -> yarl.URL:
|
|
170
|
+
"""Serialize the URI, preserving the already-encoded query string as-is."""
|
|
134
171
|
return yarl.URL.build(
|
|
135
172
|
scheme=uri.scheme or "",
|
|
136
173
|
host=uri.host,
|
|
@@ -138,10 +175,11 @@ class AIOHTTPClient(HTTPClient):
|
|
|
138
175
|
user=uri.username,
|
|
139
176
|
password=uri.password,
|
|
140
177
|
path=uri.path or "",
|
|
178
|
+
query_string=uri.query or "",
|
|
141
179
|
encoded=True,
|
|
142
180
|
)
|
|
143
181
|
|
|
144
|
-
|
|
182
|
+
def _marshal_response(
|
|
145
183
|
self, aiohttp_resp: "aiohttp.ClientResponse"
|
|
146
184
|
) -> HTTPResponseInterface:
|
|
147
185
|
"""Convert a ``aiohttp.ClientResponse`` to a ``smithy_http.aio.HTTPResponse``"""
|
|
@@ -159,7 +197,7 @@ class AIOHTTPClient(HTTPClient):
|
|
|
159
197
|
return HTTPResponse(
|
|
160
198
|
status=aiohttp_resp.status,
|
|
161
199
|
fields=headers,
|
|
162
|
-
body=
|
|
200
|
+
body=_AIOHTTPStreamingBody(aiohttp_resp),
|
|
163
201
|
reason=aiohttp_resp.reason,
|
|
164
202
|
)
|
|
165
203
|
|
|
@@ -1,12 +1,13 @@
|
|
|
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
|
+
from asyncio import gather
|
|
4
5
|
from collections.abc import AsyncGenerator, AsyncIterable
|
|
5
6
|
from copy import deepcopy
|
|
6
7
|
from dataclasses import dataclass
|
|
7
8
|
from inspect import iscoroutinefunction
|
|
8
9
|
from io import BytesIO
|
|
9
|
-
from typing import TYPE_CHECKING, Any
|
|
10
|
+
from typing import TYPE_CHECKING, Any, Self
|
|
10
11
|
|
|
11
12
|
from awscrt.exceptions import AwsCrtError
|
|
12
13
|
|
|
@@ -164,6 +165,7 @@ class AWSCRTHTTPClient(http_aio_interfaces.HTTPClient):
|
|
|
164
165
|
self._tls_ctx = crt_io.ClientTlsContext(crt_io.TlsContextOptions())
|
|
165
166
|
self._socket_options = crt_io.SocketOptions()
|
|
166
167
|
self._connections: ConnectionPoolDict = {}
|
|
168
|
+
self._closed = False
|
|
167
169
|
|
|
168
170
|
async def send(
|
|
169
171
|
self,
|
|
@@ -176,6 +178,11 @@ class AWSCRTHTTPClient(http_aio_interfaces.HTTPClient):
|
|
|
176
178
|
:param request: The request including destination URI, fields, payload.
|
|
177
179
|
:param request_config: Configuration specific to this request.
|
|
178
180
|
"""
|
|
181
|
+
if self._closed:
|
|
182
|
+
raise SmithyHTTPError(
|
|
183
|
+
"Cannot send a request after the HTTP client has been closed."
|
|
184
|
+
)
|
|
185
|
+
|
|
179
186
|
try:
|
|
180
187
|
crt_request = self._marshal_request(request)
|
|
181
188
|
connection = await self._get_connection(request.destination)
|
|
@@ -199,6 +206,26 @@ class AWSCRTHTTPClient(http_aio_interfaces.HTTPClient):
|
|
|
199
206
|
raise _CRTTimeoutError(f"CRT {e.name}: {e.message}") from e
|
|
200
207
|
raise
|
|
201
208
|
|
|
209
|
+
async def close(self) -> None:
|
|
210
|
+
"""Close all pooled HTTP connections."""
|
|
211
|
+
if self._closed:
|
|
212
|
+
return
|
|
213
|
+
self._closed = True
|
|
214
|
+
connections = tuple(self._connections.values())
|
|
215
|
+
self._connections.clear()
|
|
216
|
+
await gather(
|
|
217
|
+
*(connection.close() for connection in connections),
|
|
218
|
+
return_exceptions=True,
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
async def __aenter__(self) -> Self:
|
|
222
|
+
if self._closed:
|
|
223
|
+
raise SmithyHTTPError("Cannot enter an HTTP client that has been closed.")
|
|
224
|
+
return self
|
|
225
|
+
|
|
226
|
+
async def __aexit__(self, exc_type: Any, exc_value: Any, traceback: Any) -> None:
|
|
227
|
+
await self.close()
|
|
228
|
+
|
|
202
229
|
async def _await_response(
|
|
203
230
|
self, stream: "AIOHttpClientStreamUnified"
|
|
204
231
|
) -> AWSCRTHTTPResponse:
|