smithy-http 0.2.1__tar.gz → 0.3.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.
Files changed (40) hide show
  1. smithy_http-0.3.0/.changes/0.3.0.json +16 -0
  2. {smithy_http-0.2.1 → smithy_http-0.3.0}/CHANGELOG.md +11 -0
  3. smithy_http-0.3.0/PKG-INFO +78 -0
  4. smithy_http-0.3.0/README.md +44 -0
  5. {smithy_http-0.2.1 → smithy_http-0.3.0}/pyproject.toml +1 -1
  6. {smithy_http-0.2.1 → smithy_http-0.3.0}/src/smithy_http/__init__.py +1 -1
  7. {smithy_http-0.2.1 → smithy_http-0.3.0}/src/smithy_http/aio/aiohttp.py +2 -0
  8. {smithy_http-0.2.1 → smithy_http-0.3.0}/src/smithy_http/aio/crt.py +23 -9
  9. {smithy_http-0.2.1 → smithy_http-0.3.0}/src/smithy_http/aio/protocols.py +8 -3
  10. smithy_http-0.3.0/src/smithy_http/testing/__init__.py +13 -0
  11. smithy_http-0.3.0/src/smithy_http/testing/mockhttp.py +102 -0
  12. smithy_http-0.3.0/src/smithy_http/testing/utils.py +31 -0
  13. smithy_http-0.2.1/PKG-INFO +0 -37
  14. smithy_http-0.2.1/README.md +0 -3
  15. {smithy_http-0.2.1 → smithy_http-0.3.0}/.changes/0.0.1.json +0 -0
  16. {smithy_http-0.2.1 → smithy_http-0.3.0}/.changes/0.1.0.json +0 -0
  17. {smithy_http-0.2.1 → smithy_http-0.3.0}/.changes/0.2.0.json +0 -0
  18. {smithy_http-0.2.1 → smithy_http-0.3.0}/.changes/0.2.1.json +0 -0
  19. {smithy_http-0.2.1 → smithy_http-0.3.0}/.gitignore +0 -0
  20. {smithy_http-0.2.1 → smithy_http-0.3.0}/NOTICE +0 -0
  21. {smithy_http-0.2.1 → smithy_http-0.3.0}/src/smithy_http/aio/__init__.py +0 -0
  22. {smithy_http-0.2.1 → smithy_http-0.3.0}/src/smithy_http/aio/auth/__init__.py +0 -0
  23. {smithy_http-0.2.1 → smithy_http-0.3.0}/src/smithy_http/aio/auth/apikey.py +0 -0
  24. {smithy_http-0.2.1 → smithy_http-0.3.0}/src/smithy_http/aio/identity/__init__.py +0 -0
  25. {smithy_http-0.2.1 → smithy_http-0.3.0}/src/smithy_http/aio/identity/apikey.py +0 -0
  26. {smithy_http-0.2.1 → smithy_http-0.3.0}/src/smithy_http/aio/interfaces/__init__.py +0 -0
  27. {smithy_http-0.2.1 → smithy_http-0.3.0}/src/smithy_http/aio/restjson.py +0 -0
  28. {smithy_http-0.2.1 → smithy_http-0.3.0}/src/smithy_http/bindings.py +0 -0
  29. {smithy_http-0.2.1 → smithy_http-0.3.0}/src/smithy_http/deserializers.py +0 -0
  30. {smithy_http-0.2.1 → smithy_http-0.3.0}/src/smithy_http/endpoints.py +0 -0
  31. {smithy_http-0.2.1 → smithy_http-0.3.0}/src/smithy_http/exceptions.py +0 -0
  32. {smithy_http-0.2.1 → smithy_http-0.3.0}/src/smithy_http/interceptors/__init__.py +0 -0
  33. {smithy_http-0.2.1 → smithy_http-0.3.0}/src/smithy_http/interceptors/user_agent.py +0 -0
  34. {smithy_http-0.2.1 → smithy_http-0.3.0}/src/smithy_http/interfaces/__init__.py +0 -0
  35. {smithy_http-0.2.1 → smithy_http-0.3.0}/src/smithy_http/plugins/__init__.py +0 -0
  36. {smithy_http-0.2.1 → smithy_http-0.3.0}/src/smithy_http/py.typed +0 -0
  37. {smithy_http-0.2.1 → smithy_http-0.3.0}/src/smithy_http/restjson.py +0 -0
  38. {smithy_http-0.2.1 → smithy_http-0.3.0}/src/smithy_http/serializers.py +0 -0
  39. {smithy_http-0.2.1 → smithy_http-0.3.0}/src/smithy_http/user_agent.py +0 -0
  40. {smithy_http-0.2.1 → smithy_http-0.3.0}/src/smithy_http/utils.py +0 -0
@@ -0,0 +1,16 @@
1
+ {
2
+ "changes": [
3
+ {
4
+ "type": "feature",
5
+ "description": "Added `MockHTTPClient` for testing SDK clients without making real HTTP requests."
6
+ },
7
+ {
8
+ "type": "enhancement",
9
+ "description": "Added timeout error detection for HTTP clients"
10
+ },
11
+ {
12
+ "type": "dependency",
13
+ "description": "Bump `smithy-core` from `~=0.1.0` to `~=0.2.0`."
14
+ }
15
+ ]
16
+ }
@@ -1,5 +1,16 @@
1
1
  # Changelog
2
2
 
3
+ ## v0.3.0
4
+
5
+ ### Features
6
+ * Added `MockHTTPClient` for testing SDK clients without making real HTTP requests.
7
+
8
+ ### Enhancements
9
+ * Added timeout error detection for HTTP clients
10
+
11
+ ### Dependencies
12
+ * Bump `smithy-core` from `~=0.1.0` to `~=0.2.0`.
13
+
3
14
  ## v0.2.1
4
15
 
5
16
  ### Bug fixes
@@ -0,0 +1,78 @@
1
+ Metadata-Version: 2.4
2
+ Name: smithy-http
3
+ Version: 0.3.0
4
+ Summary: HTTP components for Smithy tooling.
5
+ Project-URL: Changelog, https://github.com/smithy-lang/smithy-python/blob/develop/packages/smithy-http/CHANGELOG.md
6
+ Project-URL: Code, https://github.com/smithy-lang/smithy-python/blob/develop/packages/smithy-http/
7
+ Project-URL: Issue tracker, https://github.com/smithy-lang/smithy-python/issues
8
+ Author: Amazon Web Services
9
+ License: Apache License 2.0
10
+ License-File: NOTICE
11
+ Keywords: http,sdk,smithy
12
+ Classifier: Development Status :: 2 - Pre-Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Intended Audience :: System Administrators
15
+ Classifier: License :: OSI Approved :: Apache Software License
16
+ Classifier: Natural Language :: English
17
+ Classifier: Operating System :: OS Independent
18
+ Classifier: Programming Language :: Python
19
+ Classifier: Programming Language :: Python :: 3
20
+ Classifier: Programming Language :: Python :: 3 :: Only
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Programming Language :: Python :: 3.13
23
+ Classifier: Programming Language :: Python :: 3.14
24
+ Classifier: Programming Language :: Python :: Implementation :: CPython
25
+ Classifier: Topic :: Software Development :: Libraries
26
+ Requires-Python: >=3.12
27
+ Requires-Dist: smithy-core~=0.2.0
28
+ Provides-Extra: aiohttp
29
+ Requires-Dist: aiohttp<4.0,>=3.11.12; extra == 'aiohttp'
30
+ Requires-Dist: yarl; extra == 'aiohttp'
31
+ Provides-Extra: awscrt
32
+ Requires-Dist: awscrt~=0.28.2; extra == 'awscrt'
33
+ Description-Content-Type: text/markdown
34
+
35
+ # smithy-http
36
+
37
+ This package provides primitives and interfaces for http functionality in tooling generated by Smithy.
38
+
39
+ ---
40
+
41
+ ## Testing
42
+
43
+ The `smithy_http.testing` module provides shared utilities for testing HTTP functionality in smithy-python clients.
44
+
45
+ ### MockHTTPClient
46
+
47
+ The `MockHTTPClient` allows you to test smithy-python clients without making actual network calls. It implements the `HTTPClient` interface and provides configurable responses for functional testing.
48
+
49
+ #### Basic Usage
50
+
51
+ ```python
52
+ from smithy_http.testing import MockHTTPClient
53
+
54
+ # Create mock client and configure responses
55
+ mock_client = MockHTTPClient()
56
+ mock_client.add_response(
57
+ status=200,
58
+ headers=[("Content-Type", "application/json")],
59
+ body=b'{"message": "success"}'
60
+ )
61
+
62
+ # Use with your smithy-python client
63
+ config = Config(transport=mock_client)
64
+ client = TestSmithyServiceClient(config=config)
65
+
66
+ # Test your client logic
67
+ result = await client.some_operation({"input": "data"})
68
+
69
+ # Inspect what requests were made
70
+ assert mock_client.call_count == 1
71
+ captured_request = mock_client.captured_requests[0]
72
+ assert result.message == "success"
73
+ ```
74
+
75
+ ### Utilities
76
+
77
+ - `create_test_request()`: Helper for creating test HTTPRequest objects
78
+ - `MockHTTPClientError`: Exception raised when no responses are queued
@@ -0,0 +1,44 @@
1
+ # smithy-http
2
+
3
+ This package provides primitives and interfaces for http functionality in tooling generated by Smithy.
4
+
5
+ ---
6
+
7
+ ## Testing
8
+
9
+ The `smithy_http.testing` module provides shared utilities for testing HTTP functionality in smithy-python clients.
10
+
11
+ ### MockHTTPClient
12
+
13
+ The `MockHTTPClient` allows you to test smithy-python clients without making actual network calls. It implements the `HTTPClient` interface and provides configurable responses for functional testing.
14
+
15
+ #### Basic Usage
16
+
17
+ ```python
18
+ from smithy_http.testing import MockHTTPClient
19
+
20
+ # Create mock client and configure responses
21
+ mock_client = MockHTTPClient()
22
+ mock_client.add_response(
23
+ status=200,
24
+ headers=[("Content-Type", "application/json")],
25
+ body=b'{"message": "success"}'
26
+ )
27
+
28
+ # Use with your smithy-python client
29
+ config = Config(transport=mock_client)
30
+ client = TestSmithyServiceClient(config=config)
31
+
32
+ # Test your client logic
33
+ result = await client.some_operation({"input": "data"})
34
+
35
+ # Inspect what requests were made
36
+ assert mock_client.call_count == 1
37
+ captured_request = mock_client.captured_requests[0]
38
+ assert result.message == "success"
39
+ ```
40
+
41
+ ### Utilities
42
+
43
+ - `create_test_request()`: Helper for creating test HTTPRequest objects
44
+ - `MockHTTPClientError`: Exception raised when no responses are queued
@@ -26,7 +26,7 @@ classifiers = [
26
26
  "Topic :: Software Development :: Libraries"
27
27
  ]
28
28
  dependencies = [
29
- "smithy-core~=0.1.0",
29
+ "smithy-core~=0.2.0",
30
30
  ]
31
31
 
32
32
  [project.urls]
@@ -6,7 +6,7 @@ from collections.abc import Iterable, Iterator
6
6
  from . import interfaces
7
7
  from .interfaces import FieldPosition
8
8
 
9
- __version__ = "0.2.1"
9
+ __version__ = "0.3.0"
10
10
 
11
11
 
12
12
  class Field(interfaces.Field):
@@ -52,6 +52,8 @@ class AIOHTTPClientConfig(HTTPClientConfiguration):
52
52
  class AIOHTTPClient(HTTPClient):
53
53
  """Implementation of :py:class:`.interfaces.HTTPClient` using aiohttp."""
54
54
 
55
+ TIMEOUT_EXCEPTIONS = (TimeoutError,)
56
+
55
57
  def __init__(
56
58
  self,
57
59
  *,
@@ -8,6 +8,8 @@ from dataclasses import dataclass
8
8
  from inspect import iscoroutinefunction
9
9
  from typing import TYPE_CHECKING, Any
10
10
 
11
+ from awscrt.exceptions import AwsCrtError
12
+
11
13
  if TYPE_CHECKING:
12
14
  # pyright doesn't like optional imports. This is reasonable because if we use these
13
15
  # in type hints then they'd result in runtime errors.
@@ -129,9 +131,16 @@ class AWSCRTHTTPClientConfig(http_interfaces.HTTPClientConfiguration):
129
131
  _assert_crt()
130
132
 
131
133
 
134
+ class _CRTTimeoutError(Exception):
135
+ """Internal wrapper for CRT timeout errors."""
136
+
137
+
132
138
  class AWSCRTHTTPClient(http_aio_interfaces.HTTPClient):
133
139
  _HTTP_PORT = 80
134
140
  _HTTPS_PORT = 443
141
+ _TIMEOUT_ERROR_NAMES = frozenset(["AWS_IO_SOCKET_TIMEOUT", "AWS_IO_SOCKET_CLOSED"])
142
+
143
+ TIMEOUT_EXCEPTIONS = (_CRTTimeoutError,)
135
144
 
136
145
  def __init__(
137
146
  self,
@@ -163,18 +172,23 @@ class AWSCRTHTTPClient(http_aio_interfaces.HTTPClient):
163
172
  :param request: The request including destination URI, fields, payload.
164
173
  :param request_config: Configuration specific to this request.
165
174
  """
166
- crt_request = self._marshal_request(request)
167
- connection = await self._get_connection(request.destination)
175
+ try:
176
+ crt_request = self._marshal_request(request)
177
+ connection = await self._get_connection(request.destination)
168
178
 
169
- # Convert body to async iterator for request_body_generator
170
- body_generator = self._create_body_generator(request.body)
179
+ # Convert body to async iterator for request_body_generator
180
+ body_generator = self._create_body_generator(request.body)
171
181
 
172
- crt_stream = connection.request(
173
- crt_request,
174
- request_body_generator=body_generator,
175
- )
182
+ crt_stream = connection.request(
183
+ crt_request,
184
+ request_body_generator=body_generator,
185
+ )
176
186
 
177
- return await self._await_response(crt_stream)
187
+ return await self._await_response(crt_stream)
188
+ except AwsCrtError as e:
189
+ if e.name in self._TIMEOUT_ERROR_NAMES:
190
+ raise _CRTTimeoutError() from e
191
+ raise
178
192
 
179
193
  async def _await_response(
180
194
  self, stream: "AIOHttpClientStreamUnified"
@@ -215,7 +215,6 @@ class HttpBindingClientProtocol(HttpClientProtocol):
215
215
  )
216
216
  return error_shape.deserialize(deserializer)
217
217
 
218
- is_throttle = response.status == 429
219
218
  message = (
220
219
  f"Unknown error for operation {operation.schema.id} "
221
220
  f"- status: {response.status}"
@@ -224,11 +223,17 @@ class HttpBindingClientProtocol(HttpClientProtocol):
224
223
  message += f" - id: {error_id}"
225
224
  if response.reason is not None:
226
225
  message += f" - reason: {response.status}"
226
+
227
+ is_timeout = response.status == 408
228
+ is_throttle = response.status == 429
229
+ fault = "client" if response.status < 500 else "server"
230
+
227
231
  return CallError(
228
232
  message=message,
229
- fault="client" if response.status < 500 else "server",
233
+ fault=fault,
230
234
  is_throttling_error=is_throttle,
231
- is_retry_safe=is_throttle or None,
235
+ is_timeout_error=is_timeout,
236
+ is_retry_safe=is_throttle or is_timeout or None,
232
237
  )
233
238
 
234
239
  def _matches_content_type(self, response: HTTPResponse) -> bool:
@@ -0,0 +1,13 @@
1
+ # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ """Shared utilities for testing smithy-python clients with an HTTP transport."""
5
+
6
+ from .mockhttp import MockHTTPClient, MockHTTPClientError
7
+ from .utils import create_test_request
8
+
9
+ __all__ = (
10
+ "MockHTTPClient",
11
+ "MockHTTPClientError",
12
+ "create_test_request",
13
+ )
@@ -0,0 +1,102 @@
1
+ # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ from collections import deque
5
+ from copy import copy
6
+ from typing import Any
7
+
8
+ from smithy_core.aio.utils import async_list
9
+
10
+ from smithy_http import tuples_to_fields
11
+ from smithy_http.aio import HTTPResponse
12
+ from smithy_http.aio.interfaces import HTTPClient, HTTPRequest
13
+ from smithy_http.interfaces import HTTPClientConfiguration, HTTPRequestConfiguration
14
+
15
+
16
+ class MockHTTPClient(HTTPClient):
17
+ """Implementation of :py:class:`.interfaces.HTTPClient` solely for testing purposes.
18
+
19
+ Simulates HTTP request/response behavior. Responses are queued in FIFO order and
20
+ requests are captured for inspection.
21
+ """
22
+
23
+ TIMEOUT_EXCEPTIONS = (TimeoutError,)
24
+
25
+ def __init__(
26
+ self,
27
+ *,
28
+ client_config: HTTPClientConfiguration | None = None,
29
+ ) -> None:
30
+ """
31
+ :param client_config: Configuration that applies to all requests made with this
32
+ client.
33
+ """
34
+ self._client_config = client_config
35
+ self._response_queue: deque[dict[str, Any]] = deque()
36
+ self._captured_requests: list[HTTPRequest] = []
37
+
38
+ def add_response(
39
+ self,
40
+ status: int = 200,
41
+ headers: list[tuple[str, str]] | None = None,
42
+ body: bytes = b"",
43
+ ) -> None:
44
+ """Queue a response for the next request.
45
+
46
+ :param status: HTTP status code.
47
+ :param headers: HTTP response headers as list of (name, value) tuples.
48
+ :param body: Response body as bytes.
49
+ """
50
+ self._response_queue.append(
51
+ {
52
+ "status": status,
53
+ "headers": headers or [],
54
+ "body": body,
55
+ }
56
+ )
57
+
58
+ async def send(
59
+ self,
60
+ request: HTTPRequest,
61
+ *,
62
+ request_config: HTTPRequestConfiguration | None = None,
63
+ ) -> HTTPResponse:
64
+ """Send HTTP request and return configured response.
65
+
66
+ :param request: The request including destination URI, fields, payload.
67
+ :param request_config: Configuration specific to this request.
68
+ :returns: Pre-configured HTTP response from the queue.
69
+ :raises MockHTTPClientError: If no responses are queued.
70
+ """
71
+ self._captured_requests.append(copy(request))
72
+
73
+ # Return next queued response or raise error
74
+ if self._response_queue:
75
+ response_data = self._response_queue.popleft()
76
+ return HTTPResponse(
77
+ status=response_data["status"],
78
+ fields=tuples_to_fields(response_data["headers"]),
79
+ body=async_list([response_data["body"]]),
80
+ reason=None,
81
+ )
82
+ else:
83
+ raise MockHTTPClientError(
84
+ "No responses queued in MockHTTPClient. Use add_response() to queue responses."
85
+ )
86
+
87
+ @property
88
+ def call_count(self) -> int:
89
+ """The number of requests made to this client."""
90
+ return len(self._captured_requests)
91
+
92
+ @property
93
+ def captured_requests(self) -> list[HTTPRequest]:
94
+ """The list of all requests captured by this client."""
95
+ return self._captured_requests.copy()
96
+
97
+ def __deepcopy__(self, memo: Any) -> "MockHTTPClient":
98
+ return self
99
+
100
+
101
+ class MockHTTPClientError(Exception):
102
+ """Exception raised by MockHTTPClient for test setup issues."""
@@ -0,0 +1,31 @@
1
+ # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ from smithy_core import URI
5
+
6
+ from smithy_http import tuples_to_fields
7
+ from smithy_http.aio import HTTPRequest
8
+
9
+
10
+ def create_test_request(
11
+ method: str = "GET",
12
+ host: str = "test.aws.dev",
13
+ path: str | None = None,
14
+ headers: list[tuple[str, str]] | None = None,
15
+ body: bytes = b"",
16
+ ) -> HTTPRequest:
17
+ """Create test HTTPRequest with defaults.
18
+
19
+ :param method: HTTP method (GET, POST, etc.)
20
+ :param host: Host name (e.g., "test.aws.dev")
21
+ :param path: Optional path (e.g., "/users")
22
+ :param headers: Optional headers as list of (name, value) tuples
23
+ :param body: Request body as bytes
24
+ :return: Configured HTTPRequest for testing
25
+ """
26
+ return HTTPRequest(
27
+ destination=URI(host=host, path=path),
28
+ method=method,
29
+ fields=tuples_to_fields(headers or []),
30
+ body=body,
31
+ )
@@ -1,37 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: smithy-http
3
- Version: 0.2.1
4
- Summary: HTTP components for Smithy tooling.
5
- Project-URL: Changelog, https://github.com/smithy-lang/smithy-python/blob/develop/packages/smithy-http/CHANGELOG.md
6
- Project-URL: Code, https://github.com/smithy-lang/smithy-python/blob/develop/packages/smithy-http/
7
- Project-URL: Issue tracker, https://github.com/smithy-lang/smithy-python/issues
8
- Author: Amazon Web Services
9
- License: Apache License 2.0
10
- License-File: NOTICE
11
- Keywords: http,sdk,smithy
12
- Classifier: Development Status :: 2 - Pre-Alpha
13
- Classifier: Intended Audience :: Developers
14
- Classifier: Intended Audience :: System Administrators
15
- Classifier: License :: OSI Approved :: Apache Software License
16
- Classifier: Natural Language :: English
17
- Classifier: Operating System :: OS Independent
18
- Classifier: Programming Language :: Python
19
- Classifier: Programming Language :: Python :: 3
20
- Classifier: Programming Language :: Python :: 3 :: Only
21
- Classifier: Programming Language :: Python :: 3.12
22
- Classifier: Programming Language :: Python :: 3.13
23
- Classifier: Programming Language :: Python :: 3.14
24
- Classifier: Programming Language :: Python :: Implementation :: CPython
25
- Classifier: Topic :: Software Development :: Libraries
26
- Requires-Python: >=3.12
27
- Requires-Dist: smithy-core~=0.1.0
28
- Provides-Extra: aiohttp
29
- Requires-Dist: aiohttp<4.0,>=3.11.12; extra == 'aiohttp'
30
- Requires-Dist: yarl; extra == 'aiohttp'
31
- Provides-Extra: awscrt
32
- Requires-Dist: awscrt~=0.28.2; extra == 'awscrt'
33
- Description-Content-Type: text/markdown
34
-
35
- # smithy-http
36
-
37
- This package provides primitives and interfaces for http functionality in tooling generated by Smithy.
@@ -1,3 +0,0 @@
1
- # smithy-http
2
-
3
- This package provides primitives and interfaces for http functionality in tooling generated by Smithy.
File without changes
File without changes