smithy-http 0.6.0__tar.gz → 0.7.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.7.0/.changes/0.7.0.json +80 -0
- {smithy_http-0.6.0 → smithy_http-0.7.0}/.gitignore +3 -0
- smithy_http-0.7.0/CHANGELOG.md +128 -0
- {smithy_http-0.6.0 → smithy_http-0.7.0}/PKG-INFO +12 -6
- {smithy_http-0.6.0 → smithy_http-0.7.0}/README.md +7 -4
- {smithy_http-0.6.0 → smithy_http-0.7.0}/pyproject.toml +5 -1
- {smithy_http-0.6.0 → smithy_http-0.7.0}/src/smithy_http/__init__.py +94 -41
- smithy_http-0.7.0/src/smithy_http/aio/__init__.py +5 -0
- {smithy_http-0.6.0 → smithy_http-0.7.0}/src/smithy_http/aio/aiohttp.py +50 -51
- {smithy_http-0.6.0/src/smithy_http/interceptors → smithy_http-0.7.0/src/smithy_http/aio/auth}/__init__.py +1 -0
- smithy_http-0.7.0/src/smithy_http/aio/auth/apikey.py +57 -0
- {smithy_http-0.6.0 → smithy_http-0.7.0}/src/smithy_http/aio/crt.py +78 -61
- {smithy_http-0.6.0/src/smithy_http/aio/auth → smithy_http-0.7.0/src/smithy_http/aio/identity}/__init__.py +1 -0
- smithy_http-0.7.0/src/smithy_http/aio/identity/apikey.py +28 -0
- smithy_http-0.7.0/src/smithy_http/aio/interfaces/__init__.py +51 -0
- smithy_http-0.7.0/src/smithy_http/aio/protocols.py +328 -0
- smithy_http-0.7.0/src/smithy_http/aio/restjson.py +24 -0
- smithy_http-0.7.0/src/smithy_http/aio/testing.py +41 -0
- smithy_http-0.7.0/src/smithy_http/auth/__init__.py +2 -0
- {smithy_http-0.6.0/src/smithy_http/aio → smithy_http-0.7.0/src/smithy_http}/auth/apikey.py +32 -30
- {smithy_http-0.6.0 → smithy_http-0.7.0}/src/smithy_http/bindings.py +2 -0
- {smithy_http-0.6.0 → smithy_http-0.7.0}/src/smithy_http/deserializers.py +60 -66
- {smithy_http-0.6.0 → smithy_http-0.7.0}/src/smithy_http/endpoints.py +2 -0
- {smithy_http-0.6.0 → smithy_http-0.7.0}/src/smithy_http/exceptions.py +2 -0
- smithy_http-0.7.0/src/smithy_http/identity/__init__.py +21 -0
- smithy_http-0.7.0/src/smithy_http/identity/apikey.py +25 -0
- {smithy_http-0.6.0/src/smithy_http/aio/identity → smithy_http-0.7.0/src/smithy_http/interceptors}/__init__.py +1 -0
- {smithy_http-0.6.0 → smithy_http-0.7.0}/src/smithy_http/interceptors/user_agent.py +3 -1
- {smithy_http-0.6.0 → smithy_http-0.7.0}/src/smithy_http/interfaces/__init__.py +112 -5
- {smithy_http-0.6.0 → smithy_http-0.7.0}/src/smithy_http/plugins/__init__.py +2 -0
- {smithy_http-0.6.0/src/smithy_http/aio → smithy_http-0.7.0/src/smithy_http}/protocols.py +297 -81
- smithy_http-0.7.0/src/smithy_http/restjson.py +89 -0
- {smithy_http-0.6.0 → smithy_http-0.7.0}/src/smithy_http/schema_extensions.py +12 -0
- {smithy_http-0.6.0 → smithy_http-0.7.0}/src/smithy_http/serializers.py +206 -149
- {smithy_http-0.6.0 → smithy_http-0.7.0}/src/smithy_http/testing/mockhttp.py +53 -52
- {smithy_http-0.6.0 → smithy_http-0.7.0}/src/smithy_http/testing/utils.py +3 -2
- smithy_http-0.7.0/src/smithy_http/urllib3.py +146 -0
- {smithy_http-0.6.0 → smithy_http-0.7.0}/src/smithy_http/user_agent.py +7 -0
- {smithy_http-0.6.0 → smithy_http-0.7.0}/src/smithy_http/utils.py +2 -0
- smithy_http-0.6.0/CHANGELOG.md +0 -97
- smithy_http-0.6.0/src/smithy_http/aio/__init__.py +0 -53
- smithy_http-0.6.0/src/smithy_http/aio/identity/apikey.py +0 -38
- smithy_http-0.6.0/src/smithy_http/aio/interfaces/__init__.py +0 -110
- smithy_http-0.6.0/src/smithy_http/aio/restjson.py +0 -60
- smithy_http-0.6.0/src/smithy_http/restjson.py +0 -29
- {smithy_http-0.6.0 → smithy_http-0.7.0}/.changes/0.0.1.json +0 -0
- {smithy_http-0.6.0 → smithy_http-0.7.0}/.changes/0.1.0.json +0 -0
- {smithy_http-0.6.0 → smithy_http-0.7.0}/.changes/0.2.0.json +0 -0
- {smithy_http-0.6.0 → smithy_http-0.7.0}/.changes/0.2.1.json +0 -0
- {smithy_http-0.6.0 → smithy_http-0.7.0}/.changes/0.3.0.json +0 -0
- {smithy_http-0.6.0 → smithy_http-0.7.0}/.changes/0.3.1.json +0 -0
- {smithy_http-0.6.0 → smithy_http-0.7.0}/.changes/0.4.0.json +0 -0
- {smithy_http-0.6.0 → smithy_http-0.7.0}/.changes/0.4.1.json +0 -0
- {smithy_http-0.6.0 → smithy_http-0.7.0}/.changes/0.4.2.json +0 -0
- {smithy_http-0.6.0 → smithy_http-0.7.0}/.changes/0.4.3.json +0 -0
- {smithy_http-0.6.0 → smithy_http-0.7.0}/.changes/0.4.4.json +0 -0
- {smithy_http-0.6.0 → smithy_http-0.7.0}/.changes/0.5.0.json +0 -0
- {smithy_http-0.6.0 → smithy_http-0.7.0}/.changes/0.6.0.json +0 -0
- {smithy_http-0.6.0 → smithy_http-0.7.0}/NOTICE +0 -0
- {smithy_http-0.6.0 → smithy_http-0.7.0}/src/smithy_http/py.typed +0 -0
- {smithy_http-0.6.0 → smithy_http-0.7.0}/src/smithy_http/testing/__init__.py +0 -0
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
{
|
|
2
|
+
"changes": [
|
|
3
|
+
{
|
|
4
|
+
"type": "breaking",
|
|
5
|
+
"description": "Replaced the async protocols with event stream mixins composed onto the sync protocols. `AsyncHttpClientProtocol`, `AsyncHttpBindingClientProtocol` and `AsyncRpcClientProtocol` are removed; use `AsyncHttpEventStreamMixin` or `AsyncRpcEventStreamMixin` with a protocol from `smithy_http.protocols`. `AsyncRpcV2CborClientProtocol` is kept as such a composition."
|
|
6
|
+
},
|
|
7
|
+
{
|
|
8
|
+
"type": "breaking",
|
|
9
|
+
"description": "Moved `HTTPRequest` and `HTTPResponse` to `smithy_http`, and their protocols and `HTTPErrorIdentifier` to `smithy_http.interfaces`. Removed `consume_body_async`."
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"type": "breaking",
|
|
13
|
+
"description": "Removed `AsyncAPIKeySigner`; `AsyncAPIKeyAuthScheme` returns the sync `APIKeySigner`. Renamed `smithy_http.aio.restjson.parse_rest_json_error_info` to `parse_rest_json_error_info_async`."
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"type": "breaking",
|
|
17
|
+
"description": "Serialized request bodies are now synchronous for both sync and async clients. `HTTPBindingSerializer`'s `async_body` parameter is replaced by `event_stream_body`, and `RawPayloadDeserializer` returns streaming payloads as the transport provides them."
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"type": "breaking",
|
|
21
|
+
"description": "Moved async implementations to `smithy_http.aio` and added the `Async` prefix to their names, for example `AsyncHTTPClient`, `AsyncHttpClientProtocol`, `AsyncHttpBindingClientProtocol`, `AsyncRpcV2CborClientProtocol` and `AsyncAPIKeyAuthScheme`. The unprefixed names are reserved for sync implementations. Update imports to the `aio` paths."
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"type": "breaking",
|
|
25
|
+
"description": "Declared explicit `__all__` exports on all public modules. Names that were previously importable only incidentally (via `from module import *` or as un-annotated module attributes) are no longer part of the public API; import public symbols by name from their documented module."
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"type": "breaking",
|
|
29
|
+
"description": "`smithy_http.testing.MockHTTPClient` is now synchronous. Async clients use `smithy_http.aio.testing.AsyncMockHTTPClient`."
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"type": "breaking",
|
|
33
|
+
"description": "Renamed `AWSCRTHTTPResponse` to `AsyncAWSCRTHTTPResponse`, and `smithy_http.aio.auth.apikey.APIKeyResolverConfig` to `AsyncAPIKeyResolverConfig`."
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"type": "feature",
|
|
37
|
+
"description": "Added synchronous HTTP support: `smithy_http.urllib3.Urllib3HTTPClient`, the sync protocols in `smithy_http.protocols`, `smithy_http.restjson.parse_rest_json_error_info` and `smithy_http.identity.apikey.APIKeyIdentityResolver`."
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"type": "feature",
|
|
41
|
+
"description": "Added `connect_timeout` and `read_timeout` to `Urllib3HTTPClientConfig`, both defaulting to 60 seconds, so sync requests no longer wait indefinitely. A request's `HTTPRequestConfiguration.read_timeout` takes precedence."
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"type": "feature",
|
|
45
|
+
"description": "Added `max_pool_connections` to `Urllib3HTTPClientConfig`, the connections kept for reuse per host, defaulting to 10 instead of urllib3's 1."
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"type": "feature",
|
|
49
|
+
"description": "Added a RpcClientProtocol base class and client-side event stream support for the rpcv2Cbor protocol."
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"type": "enhancement",
|
|
53
|
+
"description": "AWSCRTHTTPClient now negotiates HTTP/2 per request for operations that require it (event streams), advertising only HTTP/2 in ALPN and pooling those connections separately from HTTP/1.1 ones, so a single client serves both HTTP/1.1 operations and HTTP/2 event streams without configuration. The client-wide force_http_2 config remains as an override."
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"type": "enhancement",
|
|
57
|
+
"description": "Added a trust_store_path option to AWSCRTHTTPClientConfig for overriding the TLS trust store with a custom CA bundle."
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"type": "enhancement",
|
|
61
|
+
"description": "Added support for Python 3.15."
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"type": "bugfix",
|
|
65
|
+
"description": "Fixed HTTP serializers sending seekable streaming payloads, such as files, without a Content-Length and sending a modeled Content-Length twice."
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"type": "bugfix",
|
|
69
|
+
"description": "Fixed API key auth setting the request path to the URI password."
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
"type": "bugfix",
|
|
73
|
+
"description": "`APIKeyIdentity` no longer includes `api_key` in its repr, which keeps it out of debug logs."
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
"type": "dependency",
|
|
77
|
+
"description": "Bump `smithy-cbor` from `~=0.1.0` to `~=0.2.0`."
|
|
78
|
+
}
|
|
79
|
+
]
|
|
80
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## v0.7.0
|
|
4
|
+
|
|
5
|
+
### Breaking Changes
|
|
6
|
+
* Replaced the async protocols with event stream mixins composed onto the sync protocols. `AsyncHttpClientProtocol`, `AsyncHttpBindingClientProtocol` and `AsyncRpcClientProtocol` are removed; use `AsyncHttpEventStreamMixin` or `AsyncRpcEventStreamMixin` with a protocol from `smithy_http.protocols`. `AsyncRpcV2CborClientProtocol` is kept as such a composition.
|
|
7
|
+
* Moved `HTTPRequest` and `HTTPResponse` to `smithy_http`, and their protocols and `HTTPErrorIdentifier` to `smithy_http.interfaces`. Removed `consume_body_async`.
|
|
8
|
+
* Removed `AsyncAPIKeySigner`; `AsyncAPIKeyAuthScheme` returns the sync `APIKeySigner`. Renamed `smithy_http.aio.restjson.parse_rest_json_error_info` to `parse_rest_json_error_info_async`.
|
|
9
|
+
* Serialized request bodies are now synchronous for both sync and async clients. `HTTPBindingSerializer`'s `async_body` parameter is replaced by `event_stream_body`, and `RawPayloadDeserializer` returns streaming payloads as the transport provides them.
|
|
10
|
+
* Moved async implementations to `smithy_http.aio` and added the `Async` prefix to their names, for example `AsyncHTTPClient`, `AsyncHttpClientProtocol`, `AsyncHttpBindingClientProtocol`, `AsyncRpcV2CborClientProtocol` and `AsyncAPIKeyAuthScheme`. The unprefixed names are reserved for sync implementations. Update imports to the `aio` paths.
|
|
11
|
+
* Declared explicit `__all__` exports on all public modules. Names that were previously importable only incidentally (via `from module import *` or as un-annotated module attributes) are no longer part of the public API; import public symbols by name from their documented module.
|
|
12
|
+
* `smithy_http.testing.MockHTTPClient` is now synchronous. Async clients use `smithy_http.aio.testing.AsyncMockHTTPClient`.
|
|
13
|
+
* Renamed `AWSCRTHTTPResponse` to `AsyncAWSCRTHTTPResponse`, and `smithy_http.aio.auth.apikey.APIKeyResolverConfig` to `AsyncAPIKeyResolverConfig`.
|
|
14
|
+
|
|
15
|
+
### Features
|
|
16
|
+
* Added synchronous HTTP support: `smithy_http.urllib3.Urllib3HTTPClient`, the sync protocols in `smithy_http.protocols`, `smithy_http.restjson.parse_rest_json_error_info` and `smithy_http.identity.apikey.APIKeyIdentityResolver`.
|
|
17
|
+
* Added `connect_timeout` and `read_timeout` to `Urllib3HTTPClientConfig`, both defaulting to 60 seconds, so sync requests no longer wait indefinitely. A request's `HTTPRequestConfiguration.read_timeout` takes precedence.
|
|
18
|
+
* Added `max_pool_connections` to `Urllib3HTTPClientConfig`, the connections kept for reuse per host, defaulting to 10 instead of urllib3's 1.
|
|
19
|
+
* Added a RpcClientProtocol base class and client-side event stream support for the rpcv2Cbor protocol.
|
|
20
|
+
|
|
21
|
+
### Enhancements
|
|
22
|
+
* AWSCRTHTTPClient now negotiates HTTP/2 per request for operations that require it (event streams), advertising only HTTP/2 in ALPN and pooling those connections separately from HTTP/1.1 ones, so a single client serves both HTTP/1.1 operations and HTTP/2 event streams without configuration. The client-wide force_http_2 config remains as an override.
|
|
23
|
+
* Added a trust_store_path option to AWSCRTHTTPClientConfig for overriding the TLS trust store with a custom CA bundle.
|
|
24
|
+
* Added support for Python 3.15.
|
|
25
|
+
|
|
26
|
+
### Bug fixes
|
|
27
|
+
* Fixed HTTP serializers sending seekable streaming payloads, such as files, without a Content-Length and sending a modeled Content-Length twice.
|
|
28
|
+
* Fixed API key auth setting the request path to the URI password.
|
|
29
|
+
* `APIKeyIdentity` no longer includes `api_key` in its repr, which keeps it out of debug logs.
|
|
30
|
+
|
|
31
|
+
### Dependencies
|
|
32
|
+
* Bump `smithy-cbor` from `~=0.1.0` to `~=0.2.0`.
|
|
33
|
+
|
|
34
|
+
## v0.6.0
|
|
35
|
+
|
|
36
|
+
### Features
|
|
37
|
+
* `HttpClientProtocol` implements `extract_response_metadata()`, reporting the HTTP status code.
|
|
38
|
+
* Added a naive `RpcV2CborClientProtocol` for the `smithy.protocols#rpcv2Cbor` protocol (optional `cbor` extra).
|
|
39
|
+
* Added schema-cached HTTP binding plans for members, operations, payloads, and request and response dispatch.
|
|
40
|
+
|
|
41
|
+
### Dependencies
|
|
42
|
+
* Bump `smithy-cbor` from `~=0.0.0` to `~=0.1.0`.
|
|
43
|
+
|
|
44
|
+
## v0.5.0
|
|
45
|
+
|
|
46
|
+
### Features
|
|
47
|
+
* Added deterministic connection-pool cleanup and async context manager support to HTTP clients.
|
|
48
|
+
|
|
49
|
+
### Bug fixes
|
|
50
|
+
* Stream aiohttp response bodies incrementally to enable true output event streams.
|
|
51
|
+
* Fixed `AIOHTTPClient` to stop dropping valueless query parameters and automatically adding a `Content-Type` to bodyless POST, PUT, and PATCH requests.
|
|
52
|
+
* Fixed duplicate Content-Type headers when a member is bound to Content-Type.
|
|
53
|
+
|
|
54
|
+
## v0.4.4
|
|
55
|
+
|
|
56
|
+
### Enhancements
|
|
57
|
+
* Added a protocol hook for extracting server-provided retry delays from error responses.
|
|
58
|
+
* Declared `AWSCRTHTTPClient` as duplex (bidirectional) streaming capable via `SUPPORTS_DUPLEX_STREAMING`. `AIOHTTPClient` explicitly does not support duplex streaming.
|
|
59
|
+
* Disabled automatic redirects in `AIOHTTPClient` so that redirect responses are returned to the caller instead of being followed transparently.
|
|
60
|
+
|
|
61
|
+
## v0.4.3
|
|
62
|
+
|
|
63
|
+
### Enhancements
|
|
64
|
+
* 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))
|
|
65
|
+
|
|
66
|
+
### Bug fixes
|
|
67
|
+
* Fixed `AIOHTTPClient` sending omitted request payloads with chunked transfer encoding.
|
|
68
|
+
|
|
69
|
+
## v0.4.2
|
|
70
|
+
|
|
71
|
+
### Bug fixes
|
|
72
|
+
* Disabled automatic response decompression in `AIOHTTPClient`.
|
|
73
|
+
|
|
74
|
+
## v0.4.1
|
|
75
|
+
|
|
76
|
+
### Bug fixes
|
|
77
|
+
* Fixed `AWSCRTHTTPClient` to send HTTP/1.1 request bodies via `body_stream` instead of the HTTP/2-only `request_body_generator`.
|
|
78
|
+
|
|
79
|
+
## v0.4.0
|
|
80
|
+
|
|
81
|
+
### Breaking Changes
|
|
82
|
+
* Replace `FieldPosition` enum values with string literals for `Field.kind`. Use "header" and "trailer" instead of `FieldPosition.HEADER` and `FieldPosition.TRAILER`.
|
|
83
|
+
|
|
84
|
+
### Dependencies
|
|
85
|
+
* Upgrade CRT to 0.32.0 to fix bug where HTTP2 exceptions could hang indefinitely
|
|
86
|
+
|
|
87
|
+
## v0.3.1
|
|
88
|
+
|
|
89
|
+
### Bug fixes
|
|
90
|
+
* Fixed empty error messaging for CRT-based timeout errors.
|
|
91
|
+
|
|
92
|
+
### Dependencies
|
|
93
|
+
* Removed strict pinning on `smithy-core` in favor of client managed versions.
|
|
94
|
+
|
|
95
|
+
## v0.3.0
|
|
96
|
+
|
|
97
|
+
### Features
|
|
98
|
+
* Added `MockHTTPClient` for testing SDK clients without making real HTTP requests.
|
|
99
|
+
|
|
100
|
+
### Enhancements
|
|
101
|
+
* Added timeout error detection for HTTP clients
|
|
102
|
+
|
|
103
|
+
### Dependencies
|
|
104
|
+
* Bump `smithy-core` from `~=0.1.0` to `~=0.2.0`.
|
|
105
|
+
|
|
106
|
+
## v0.2.1
|
|
107
|
+
|
|
108
|
+
### Bug fixes
|
|
109
|
+
* Add port to CRT HTTP client's host header.
|
|
110
|
+
|
|
111
|
+
## v0.2.0
|
|
112
|
+
|
|
113
|
+
### Breaking Changes
|
|
114
|
+
* Update `AWSCRTHTTPClient` to integrate with the new AWS CRT async interfaces. ([#573](https://github.com/smithy-lang/smithy-python/pull/573)). The `AWSCRTHTTPResponse` constructor now accepts a `stream` argument of type `awscrt.aio.http.AIOHttpClientStreamUnified`. Additionally, the following classes were removed: `CRTResponseBody`, `CRTResponseFactory`, and `BufferableByteStream`.
|
|
115
|
+
|
|
116
|
+
## v0.1.0
|
|
117
|
+
|
|
118
|
+
### Breaking Changes
|
|
119
|
+
* Removed identity and auth interfaces in favor of the transport-agnostic interfaces introduced in `smithy-core`.
|
|
120
|
+
|
|
121
|
+
### Features
|
|
122
|
+
* Introduced schema-based serializers and deserializers for HTTP binding protocols.
|
|
123
|
+
|
|
124
|
+
## v0.0.1
|
|
125
|
+
|
|
126
|
+
### Features
|
|
127
|
+
* Added support for aiohttp and AWSCRT http clients.
|
|
128
|
+
* Added basic HTTP primitives and interfaces for Smithy clients.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: smithy-http
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.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/
|
|
@@ -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 :: 3.15
|
|
24
25
|
Classifier: Programming Language :: Python :: Free Threading :: 2 - Beta
|
|
25
26
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
26
27
|
Classifier: Topic :: Software Development :: Libraries
|
|
@@ -32,7 +33,9 @@ Requires-Dist: yarl; extra == 'aiohttp'
|
|
|
32
33
|
Provides-Extra: awscrt
|
|
33
34
|
Requires-Dist: awscrt~=0.32.0; extra == 'awscrt'
|
|
34
35
|
Provides-Extra: cbor
|
|
35
|
-
Requires-Dist: smithy-cbor~=0.
|
|
36
|
+
Requires-Dist: smithy-cbor~=0.2.0; extra == 'cbor'
|
|
37
|
+
Provides-Extra: urllib3
|
|
38
|
+
Requires-Dist: urllib3<3.0,>=2.0; extra == 'urllib3'
|
|
36
39
|
Description-Content-Type: text/markdown
|
|
37
40
|
|
|
38
41
|
# smithy-http
|
|
@@ -47,11 +50,14 @@ The `smithy_http.testing` module provides shared utilities for testing HTTP func
|
|
|
47
50
|
|
|
48
51
|
### MockHTTPClient
|
|
49
52
|
|
|
50
|
-
|
|
53
|
+
`MockHTTPClient` lets you test smithy-python clients without making actual network
|
|
54
|
+
calls. It implements the `HTTPClient` interface and returns queued responses. For async
|
|
55
|
+
clients, use `smithy_http.aio.testing.AsyncMockHTTPClient`, which implements
|
|
56
|
+
`AsyncHTTPClient` the same way.
|
|
51
57
|
|
|
52
|
-
#### Basic Usage
|
|
58
|
+
#### Basic Usage
|
|
53
59
|
|
|
54
|
-
```python
|
|
60
|
+
```python
|
|
55
61
|
from smithy_http.testing import MockHTTPClient
|
|
56
62
|
|
|
57
63
|
# Create mock client and configure responses
|
|
@@ -67,7 +73,7 @@ config = Config(transport=mock_client)
|
|
|
67
73
|
client = TestSmithyServiceClient(config=config)
|
|
68
74
|
|
|
69
75
|
# Test your client logic
|
|
70
|
-
result =
|
|
76
|
+
result = client.some_operation({"input": "data"})
|
|
71
77
|
|
|
72
78
|
# Inspect what requests were made
|
|
73
79
|
assert mock_client.call_count == 1
|
|
@@ -10,11 +10,14 @@ The `smithy_http.testing` module provides shared utilities for testing HTTP func
|
|
|
10
10
|
|
|
11
11
|
### MockHTTPClient
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
`MockHTTPClient` lets you test smithy-python clients without making actual network
|
|
14
|
+
calls. It implements the `HTTPClient` interface and returns queued responses. For async
|
|
15
|
+
clients, use `smithy_http.aio.testing.AsyncMockHTTPClient`, which implements
|
|
16
|
+
`AsyncHTTPClient` the same way.
|
|
14
17
|
|
|
15
|
-
#### Basic Usage
|
|
18
|
+
#### Basic Usage
|
|
16
19
|
|
|
17
|
-
```python
|
|
20
|
+
```python
|
|
18
21
|
from smithy_http.testing import MockHTTPClient
|
|
19
22
|
|
|
20
23
|
# Create mock client and configure responses
|
|
@@ -30,7 +33,7 @@ config = Config(transport=mock_client)
|
|
|
30
33
|
client = TestSmithyServiceClient(config=config)
|
|
31
34
|
|
|
32
35
|
# Test your client logic
|
|
33
|
-
result =
|
|
36
|
+
result = client.some_operation({"input": "data"})
|
|
34
37
|
|
|
35
38
|
# Inspect what requests were made
|
|
36
39
|
assert mock_client.call_count == 1
|
|
@@ -22,6 +22,7 @@ classifiers = [
|
|
|
22
22
|
"Programming Language :: Python :: 3.12",
|
|
23
23
|
"Programming Language :: Python :: 3.13",
|
|
24
24
|
"Programming Language :: Python :: 3.14",
|
|
25
|
+
"Programming Language :: Python :: 3.15",
|
|
25
26
|
"Programming Language :: Python :: Implementation :: CPython",
|
|
26
27
|
"Programming Language :: Python :: Free Threading :: 2 - Beta",
|
|
27
28
|
"Topic :: Software Development :: Libraries"
|
|
@@ -40,12 +41,15 @@ awscrt = [
|
|
|
40
41
|
"awscrt~=0.32.0",
|
|
41
42
|
]
|
|
42
43
|
cbor = [
|
|
43
|
-
"smithy-cbor~=0.
|
|
44
|
+
"smithy-cbor~=0.2.0",
|
|
44
45
|
]
|
|
45
46
|
aiohttp = [
|
|
46
47
|
"aiohttp>=3.11.12, <4.0",
|
|
47
48
|
"yarl"
|
|
48
49
|
]
|
|
50
|
+
urllib3 = [
|
|
51
|
+
"urllib3>=2.0, <3.0",
|
|
52
|
+
]
|
|
49
53
|
|
|
50
54
|
[build-system]
|
|
51
55
|
requires = ["hatchling"]
|
|
@@ -2,12 +2,27 @@
|
|
|
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 dataclasses import dataclass, field
|
|
5
6
|
from typing import get_args
|
|
6
7
|
|
|
8
|
+
from smithy_core.interfaces import AnyStreamingBlob, URI
|
|
9
|
+
from smithy_core.utils import read_streaming_blob
|
|
10
|
+
|
|
7
11
|
from . import interfaces
|
|
8
12
|
from .interfaces import FieldPosition
|
|
9
13
|
|
|
10
|
-
|
|
14
|
+
__all__ = [
|
|
15
|
+
"Field",
|
|
16
|
+
"FieldPosition",
|
|
17
|
+
"Fields",
|
|
18
|
+
"HTTPRequest",
|
|
19
|
+
"HTTPResponse",
|
|
20
|
+
"interfaces",
|
|
21
|
+
"quote_and_escape_field_value",
|
|
22
|
+
"tuples_to_fields",
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
__version__ = "0.7.0"
|
|
11
26
|
|
|
12
27
|
_VALID_FIELD_POSITIONS = frozenset(get_args(FieldPosition))
|
|
13
28
|
|
|
@@ -35,6 +50,22 @@ class Field(interfaces.Field):
|
|
|
35
50
|
raise ValueError(f"Unknown field kind: {kind!r}")
|
|
36
51
|
self.kind = kind
|
|
37
52
|
|
|
53
|
+
def __eq__(self, other: object) -> bool:
|
|
54
|
+
"""Name, values, and kind must match.
|
|
55
|
+
|
|
56
|
+
Values order must match.
|
|
57
|
+
"""
|
|
58
|
+
if not isinstance(other, Field):
|
|
59
|
+
return False
|
|
60
|
+
return (
|
|
61
|
+
self.name == other.name
|
|
62
|
+
and self.kind == other.kind
|
|
63
|
+
and self.values == other.values
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
def __repr__(self) -> str:
|
|
67
|
+
return f"Field(name={self.name!r}, value={self.values!r}, kind={self.kind!r})"
|
|
68
|
+
|
|
38
69
|
def add(self, value: str) -> None:
|
|
39
70
|
"""Append a value to a field."""
|
|
40
71
|
self.values.append(value)
|
|
@@ -75,22 +106,6 @@ class Field(interfaces.Field):
|
|
|
75
106
|
value."""
|
|
76
107
|
return [(self.name, val) for val in self.values]
|
|
77
108
|
|
|
78
|
-
def __eq__(self, other: object) -> bool:
|
|
79
|
-
"""Name, values, and kind must match.
|
|
80
|
-
|
|
81
|
-
Values order must match.
|
|
82
|
-
"""
|
|
83
|
-
if not isinstance(other, Field):
|
|
84
|
-
return False
|
|
85
|
-
return (
|
|
86
|
-
self.name == other.name
|
|
87
|
-
and self.kind == other.kind
|
|
88
|
-
and self.values == other.values
|
|
89
|
-
)
|
|
90
|
-
|
|
91
|
-
def __repr__(self) -> str:
|
|
92
|
-
return f"Field(name={self.name!r}, value={self.values!r}, kind={self.kind!r})"
|
|
93
|
-
|
|
94
109
|
|
|
95
110
|
class Fields(interfaces.Fields):
|
|
96
111
|
def __init__(
|
|
@@ -123,10 +138,6 @@ class Fields(interfaces.Fields):
|
|
|
123
138
|
self.entries: dict[str, interfaces.Field] = OrderedDict(init_tuples)
|
|
124
139
|
self.encoding: str = encoding
|
|
125
140
|
|
|
126
|
-
def set_field(self, field: interfaces.Field) -> None:
|
|
127
|
-
"""Alias for __setitem__ to utilize the field.name for the entry key."""
|
|
128
|
-
self.__setitem__(field.name, field)
|
|
129
|
-
|
|
130
141
|
def __setitem__(self, name: str, field: interfaces.Field) -> None:
|
|
131
142
|
"""Set or override entry for a Field name."""
|
|
132
143
|
normalized_name = self._normalize_field_name(name)
|
|
@@ -138,11 +149,6 @@ class Fields(interfaces.Fields):
|
|
|
138
149
|
)
|
|
139
150
|
self.entries[normalized_name] = field
|
|
140
151
|
|
|
141
|
-
def get(
|
|
142
|
-
self, key: str, default: interfaces.Field | None = None
|
|
143
|
-
) -> interfaces.Field | None:
|
|
144
|
-
return self[key] if key in self else default
|
|
145
|
-
|
|
146
152
|
def __getitem__(self, name: str) -> interfaces.Field:
|
|
147
153
|
"""Retrieve Field entry."""
|
|
148
154
|
normalized_name = self._normalize_field_name(name)
|
|
@@ -153,6 +159,36 @@ class Fields(interfaces.Fields):
|
|
|
153
159
|
normalized_name = self._normalize_field_name(name)
|
|
154
160
|
del self.entries[normalized_name]
|
|
155
161
|
|
|
162
|
+
def __eq__(self, other: object) -> bool:
|
|
163
|
+
"""Encoding must match.
|
|
164
|
+
|
|
165
|
+
Entries must match in values and order.
|
|
166
|
+
"""
|
|
167
|
+
if not isinstance(other, Fields):
|
|
168
|
+
return False
|
|
169
|
+
return self.encoding == other.encoding and self.entries == other.entries
|
|
170
|
+
|
|
171
|
+
def __iter__(self) -> Iterator[interfaces.Field]:
|
|
172
|
+
yield from self.entries.values()
|
|
173
|
+
|
|
174
|
+
def __len__(self) -> int:
|
|
175
|
+
return len(self.entries)
|
|
176
|
+
|
|
177
|
+
def __repr__(self) -> str:
|
|
178
|
+
return f"Fields({self.entries})"
|
|
179
|
+
|
|
180
|
+
def __contains__(self, key: str) -> bool:
|
|
181
|
+
return self._normalize_field_name(key) in self.entries
|
|
182
|
+
|
|
183
|
+
def set_field(self, field: interfaces.Field) -> None:
|
|
184
|
+
"""Alias for __setitem__ to utilize the field.name for the entry key."""
|
|
185
|
+
self.__setitem__(field.name, field)
|
|
186
|
+
|
|
187
|
+
def get(
|
|
188
|
+
self, key: str, default: interfaces.Field | None = None
|
|
189
|
+
) -> interfaces.Field | None:
|
|
190
|
+
return self[key] if key in self else default
|
|
191
|
+
|
|
156
192
|
def get_by_type(self, kind: FieldPosition) -> list[interfaces.Field]:
|
|
157
193
|
"""Helper function for retrieving specific types of fields.
|
|
158
194
|
|
|
@@ -182,26 +218,43 @@ class Fields(interfaces.Fields):
|
|
|
182
218
|
"""
|
|
183
219
|
return name.lower()
|
|
184
220
|
|
|
185
|
-
def __eq__(self, other: object) -> bool:
|
|
186
|
-
"""Encoding must match.
|
|
187
221
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
return False
|
|
192
|
-
return self.encoding == other.encoding and self.entries == other.entries
|
|
222
|
+
@dataclass(kw_only=True)
|
|
223
|
+
class HTTPRequest(interfaces.HTTPRequest):
|
|
224
|
+
"""HTTP primitives for an Exchange to construct a version agnostic HTTP message."""
|
|
193
225
|
|
|
194
|
-
|
|
195
|
-
|
|
226
|
+
destination: URI
|
|
227
|
+
body: AnyStreamingBlob = field(repr=False, default=b"")
|
|
228
|
+
method: str
|
|
229
|
+
fields: interfaces.Fields
|
|
230
|
+
http2_required: bool = False
|
|
231
|
+
"""Transport hint: negotiate HTTP/2 for this request and don't reuse a non-h2
|
|
232
|
+
connection for it. Set for event-stream operations whose protocol permits h2;
|
|
233
|
+
not sent on the wire."""
|
|
196
234
|
|
|
197
|
-
def __len__(self) -> int:
|
|
198
|
-
return len(self.entries)
|
|
199
235
|
|
|
200
|
-
|
|
201
|
-
|
|
236
|
+
# CAUTION: implements interfaces.HTTPResponse structurally; subclassing the protocol
|
|
237
|
+
# makes its read-only properties collide with the dataclass fields (AttributeError).
|
|
238
|
+
# See https://github.com/python/typing/discussions/903#discussioncomment-4866851.
|
|
239
|
+
@dataclass(kw_only=True)
|
|
240
|
+
class HTTPResponse:
|
|
241
|
+
"""Basic implementation of :py:class:`.interfaces.HTTPResponse`."""
|
|
202
242
|
|
|
203
|
-
|
|
204
|
-
|
|
243
|
+
body: AnyStreamingBlob = field(repr=False, default=b"")
|
|
244
|
+
"""The response payload."""
|
|
245
|
+
|
|
246
|
+
status: int
|
|
247
|
+
"""The 3 digit response status code (1xx, 2xx, 3xx, 4xx, 5xx)."""
|
|
248
|
+
|
|
249
|
+
fields: interfaces.Fields
|
|
250
|
+
"""HTTP header and trailer fields."""
|
|
251
|
+
|
|
252
|
+
reason: str | None = None
|
|
253
|
+
"""Optional string provided by the server explaining the status."""
|
|
254
|
+
|
|
255
|
+
def consume_body(self) -> bytes:
|
|
256
|
+
"""Read the response body into bytes."""
|
|
257
|
+
return read_streaming_blob(self.body)
|
|
205
258
|
|
|
206
259
|
|
|
207
260
|
def quote_and_escape_field_value(value: str) -> str:
|
|
@@ -25,22 +25,19 @@ from smithy_core.aio.types import AsyncBytesReader
|
|
|
25
25
|
from smithy_core.exceptions import MissingDependencyError
|
|
26
26
|
from smithy_core.interfaces import URI
|
|
27
27
|
|
|
28
|
-
from
|
|
28
|
+
from smithy_http import HTTPResponse
|
|
29
|
+
from smithy_http.interfaces import HTTPRequest
|
|
30
|
+
from smithy_http.interfaces import HTTPResponse as HTTPResponseInterface
|
|
31
|
+
|
|
32
|
+
from .. import tuples_to_fields
|
|
29
33
|
from ..exceptions import SmithyHTTPError
|
|
30
34
|
from ..interfaces import (
|
|
31
35
|
HTTPClientConfiguration,
|
|
32
36
|
HTTPRequestConfiguration,
|
|
33
37
|
)
|
|
34
|
-
from . import
|
|
35
|
-
from .interfaces import HTTPClient, HTTPRequest
|
|
36
|
-
from .interfaces import HTTPResponse as HTTPResponseInterface
|
|
38
|
+
from .interfaces import AsyncHTTPClient
|
|
37
39
|
|
|
38
|
-
|
|
39
|
-
def _assert_aiohttp() -> None:
|
|
40
|
-
if not HAS_AIOHTTP:
|
|
41
|
-
raise MissingDependencyError(
|
|
42
|
-
"Attempted to use aiohttp component, but aiohttp is not installed."
|
|
43
|
-
)
|
|
40
|
+
__all__ = ["AIOHTTPClient", "AIOHTTPClientConfig"]
|
|
44
41
|
|
|
45
42
|
|
|
46
43
|
class AIOHTTPClientConfig(HTTPClientConfiguration):
|
|
@@ -48,23 +45,8 @@ class AIOHTTPClientConfig(HTTPClientConfiguration):
|
|
|
48
45
|
_assert_aiohttp()
|
|
49
46
|
|
|
50
47
|
|
|
51
|
-
class
|
|
52
|
-
"""
|
|
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
|
-
|
|
66
|
-
class AIOHTTPClient(HTTPClient):
|
|
67
|
-
"""Implementation of :py:class:`.interfaces.HTTPClient` using aiohttp."""
|
|
48
|
+
class AIOHTTPClient(AsyncHTTPClient):
|
|
49
|
+
"""Implementation of :py:class:`.interfaces.AsyncHTTPClient` using aiohttp."""
|
|
68
50
|
|
|
69
51
|
TIMEOUT_EXCEPTIONS = (TimeoutError,)
|
|
70
52
|
|
|
@@ -93,6 +75,20 @@ class AIOHTTPClient(HTTPClient):
|
|
|
93
75
|
headers={"Accept-Encoding": "identity"},
|
|
94
76
|
)
|
|
95
77
|
|
|
78
|
+
async def __aenter__(self) -> Self:
|
|
79
|
+
if self._closed:
|
|
80
|
+
raise SmithyHTTPError("Cannot enter an HTTP client that has been closed.")
|
|
81
|
+
return self
|
|
82
|
+
|
|
83
|
+
async def __aexit__(self, exc_type: Any, exc_value: Any, traceback: Any) -> None:
|
|
84
|
+
await self.close()
|
|
85
|
+
|
|
86
|
+
def __deepcopy__(self, memo: Any) -> "AIOHTTPClient":
|
|
87
|
+
return AIOHTTPClient(
|
|
88
|
+
client_config=deepcopy(self._config),
|
|
89
|
+
_session=copy(self._session),
|
|
90
|
+
)
|
|
91
|
+
|
|
96
92
|
async def send(
|
|
97
93
|
self,
|
|
98
94
|
request: HTTPRequest,
|
|
@@ -145,14 +141,6 @@ class AIOHTTPClient(HTTPClient):
|
|
|
145
141
|
self._closed = True
|
|
146
142
|
await self._session.close()
|
|
147
143
|
|
|
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()
|
|
155
|
-
|
|
156
144
|
async def _prepare_body(self, body: StreamingBlob) -> AsyncBytesReader | None:
|
|
157
145
|
"""Convert a body for aiohttp, omitting seekable bodies with no data."""
|
|
158
146
|
if not isinstance(body, AsyncBytesReader):
|
|
@@ -183,26 +171,37 @@ class AIOHTTPClient(HTTPClient):
|
|
|
183
171
|
self, aiohttp_resp: "aiohttp.ClientResponse"
|
|
184
172
|
) -> HTTPResponseInterface:
|
|
185
173
|
"""Convert a ``aiohttp.ClientResponse`` to a ``smithy_http.aio.HTTPResponse``"""
|
|
186
|
-
headers = Fields()
|
|
187
|
-
for header_name, header_val in aiohttp_resp.headers.items():
|
|
188
|
-
try:
|
|
189
|
-
headers[header_name].add(header_val)
|
|
190
|
-
except KeyError:
|
|
191
|
-
headers[header_name] = Field(
|
|
192
|
-
name=header_name,
|
|
193
|
-
values=[header_val],
|
|
194
|
-
kind="header",
|
|
195
|
-
)
|
|
196
|
-
|
|
197
174
|
return HTTPResponse(
|
|
198
175
|
status=aiohttp_resp.status,
|
|
199
|
-
fields=headers,
|
|
176
|
+
fields=tuples_to_fields(aiohttp_resp.headers.items()),
|
|
200
177
|
body=_AIOHTTPStreamingBody(aiohttp_resp),
|
|
201
178
|
reason=aiohttp_resp.reason,
|
|
202
179
|
)
|
|
203
180
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
181
|
+
|
|
182
|
+
def _assert_aiohttp() -> None:
|
|
183
|
+
if not HAS_AIOHTTP:
|
|
184
|
+
raise MissingDependencyError(
|
|
185
|
+
"Attempted to use aiohttp component, but aiohttp is not installed."
|
|
208
186
|
)
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
class _AIOHTTPStreamingBody(AsyncIterable[bytes]):
|
|
190
|
+
"""Streams a response body, releasing the response once it is done."""
|
|
191
|
+
|
|
192
|
+
def __init__(self, response: "aiohttp.ClientResponse") -> None:
|
|
193
|
+
self._response = response
|
|
194
|
+
self._reader: AsyncBytesReader | None = None
|
|
195
|
+
|
|
196
|
+
def __aiter__(self) -> AsyncIterator[bytes]:
|
|
197
|
+
# The reader iterates by line, so use iter_any to get raw chunks.
|
|
198
|
+
return self._response.content.iter_any()
|
|
199
|
+
|
|
200
|
+
async def read(self, size: int = -1) -> bytes:
|
|
201
|
+
if self._reader is None:
|
|
202
|
+
self._reader = AsyncBytesReader(self._response.content.iter_any())
|
|
203
|
+
return await self._reader.read(size)
|
|
204
|
+
|
|
205
|
+
async def close(self) -> None:
|
|
206
|
+
# Pools the connection if the body was read to completion, closes it if not.
|
|
207
|
+
self._response.release()
|