smithy-http 0.0.1__py3-none-any.whl
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/__init__.py +233 -0
- smithy_http/aio/__init__.py +53 -0
- smithy_http/aio/aiohttp.py +140 -0
- smithy_http/aio/auth/__init__.py +2 -0
- smithy_http/aio/auth/apikey.py +117 -0
- smithy_http/aio/crt.py +493 -0
- smithy_http/aio/identity/__init__.py +2 -0
- smithy_http/aio/identity/apikey.py +38 -0
- smithy_http/aio/interfaces/__init__.py +85 -0
- smithy_http/aio/interfaces/auth.py +107 -0
- smithy_http/aio/protocols.py +118 -0
- smithy_http/aio/restjson.py +60 -0
- smithy_http/deserializers.py +259 -0
- smithy_http/endpoints.py +30 -0
- smithy_http/exceptions.py +7 -0
- smithy_http/interceptors/__init__.py +2 -0
- smithy_http/interceptors/user_agent.py +153 -0
- smithy_http/interfaces/__init__.py +141 -0
- smithy_http/plugins/__init__.py +15 -0
- smithy_http/py.typed +0 -0
- smithy_http/restjson.py +29 -0
- smithy_http/serializers.py +631 -0
- smithy_http/user_agent.py +97 -0
- smithy_http/utils.py +125 -0
- smithy_http-0.0.1.dist-info/METADATA +12 -0
- smithy_http-0.0.1.dist-info/RECORD +28 -0
- smithy_http-0.0.1.dist-info/WHEEL +4 -0
- smithy_http-0.0.1.dist-info/licenses/NOTICE +1 -0
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
import platform
|
|
4
|
+
from typing import Any, Self
|
|
5
|
+
|
|
6
|
+
import smithy_core
|
|
7
|
+
from smithy_core.interceptors import InputContext, Interceptor, RequestContext
|
|
8
|
+
from smithy_core.types import PropertyKey
|
|
9
|
+
|
|
10
|
+
from smithy_http import Field
|
|
11
|
+
from smithy_http.aio.interfaces import HTTPRequest
|
|
12
|
+
from smithy_http.user_agent import UserAgent, UserAgentComponent
|
|
13
|
+
|
|
14
|
+
USER_AGENT = PropertyKey(key="user_agent", value_type=UserAgent)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class UserAgentInterceptor(Interceptor[Any, Any, HTTPRequest, None]):
|
|
18
|
+
"""Adds interceptors that initialize UserAgent in the context and add the user-agent
|
|
19
|
+
header."""
|
|
20
|
+
|
|
21
|
+
def read_before_execution(self, context: InputContext[Any]) -> None:
|
|
22
|
+
context.properties[USER_AGENT] = _UserAgentBuilder.from_environment().build()
|
|
23
|
+
|
|
24
|
+
def modify_before_signing(
|
|
25
|
+
self, context: RequestContext[Any, HTTPRequest]
|
|
26
|
+
) -> HTTPRequest:
|
|
27
|
+
user_agent = context.properties[USER_AGENT]
|
|
28
|
+
request = context.transport_request
|
|
29
|
+
request.fields.set_field(Field(name="User-Agent", values=[str(user_agent)]))
|
|
30
|
+
return context.transport_request
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
_USERAGENT_ALLOWED_OS_NAMES = (
|
|
34
|
+
"windows",
|
|
35
|
+
"linux",
|
|
36
|
+
"macos",
|
|
37
|
+
"android",
|
|
38
|
+
"ios",
|
|
39
|
+
"watchos",
|
|
40
|
+
"tvos",
|
|
41
|
+
"other",
|
|
42
|
+
)
|
|
43
|
+
_USERAGENT_PLATFORM_NAME_MAPPINGS = {"darwin": "macos"}
|
|
44
|
+
_USERAGENT_SDK_NAME = "python"
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class _UserAgentBuilder:
|
|
48
|
+
def __init__(
|
|
49
|
+
self,
|
|
50
|
+
*,
|
|
51
|
+
platform_name: str | None,
|
|
52
|
+
platform_version: str | None,
|
|
53
|
+
platform_machine: str | None,
|
|
54
|
+
python_version: str | None,
|
|
55
|
+
python_implementation: str | None,
|
|
56
|
+
sdk_version: str | None,
|
|
57
|
+
) -> None:
|
|
58
|
+
self._platform_name = platform_name
|
|
59
|
+
self._platform_version = platform_version
|
|
60
|
+
self._platform_machine = platform_machine
|
|
61
|
+
self._python_version = python_version
|
|
62
|
+
self._python_implementation = python_implementation
|
|
63
|
+
self._sdk_version = sdk_version
|
|
64
|
+
|
|
65
|
+
@classmethod
|
|
66
|
+
def from_environment(cls) -> Self:
|
|
67
|
+
return cls(
|
|
68
|
+
platform_name=platform.system(),
|
|
69
|
+
platform_version=platform.release(),
|
|
70
|
+
platform_machine=platform.machine(),
|
|
71
|
+
python_version=platform.python_version(),
|
|
72
|
+
python_implementation=platform.python_implementation(),
|
|
73
|
+
sdk_version=smithy_core.__version__,
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
def build(self) -> UserAgent:
|
|
77
|
+
user_agent = UserAgent()
|
|
78
|
+
user_agent.sdk_metadata.extend(self._build_sdk_metadata())
|
|
79
|
+
user_agent.ua_metadata.append(UserAgentComponent(prefix="ua", name="2.1"))
|
|
80
|
+
user_agent.os_metadata.extend(self._build_os_metadata())
|
|
81
|
+
user_agent.os_metadata.extend(self._build_architecture_metadata())
|
|
82
|
+
user_agent.language_metadata.extend(self._build_language_metadata())
|
|
83
|
+
|
|
84
|
+
return user_agent
|
|
85
|
+
|
|
86
|
+
def _build_sdk_metadata(self) -> list[UserAgentComponent]:
|
|
87
|
+
if self._sdk_version:
|
|
88
|
+
return [
|
|
89
|
+
UserAgentComponent(prefix=_USERAGENT_SDK_NAME, name=self._sdk_version)
|
|
90
|
+
]
|
|
91
|
+
return []
|
|
92
|
+
|
|
93
|
+
def _build_os_metadata(self) -> list[UserAgentComponent]:
|
|
94
|
+
"""Build the OS/platform components of the User-Agent header string.
|
|
95
|
+
|
|
96
|
+
For recognized platform names that match or map to an entry in the list
|
|
97
|
+
of standardized OS names, a single component with prefix "os" is
|
|
98
|
+
returned. Otherwise, one component "os/other" is returned and a second
|
|
99
|
+
with prefix "md" and the raw platform name.
|
|
100
|
+
|
|
101
|
+
String representations of example return values:
|
|
102
|
+
* ``os/macos#10.13.6``
|
|
103
|
+
* ``os/linux``
|
|
104
|
+
* ``os/other``
|
|
105
|
+
* ``os/other md/foobar#1.2.3``
|
|
106
|
+
"""
|
|
107
|
+
if self._platform_name in (None, ""):
|
|
108
|
+
return [UserAgentComponent("os", "other")]
|
|
109
|
+
|
|
110
|
+
plt_name_lower = self._platform_name.lower()
|
|
111
|
+
if plt_name_lower in _USERAGENT_ALLOWED_OS_NAMES:
|
|
112
|
+
os_family = plt_name_lower
|
|
113
|
+
elif plt_name_lower in _USERAGENT_PLATFORM_NAME_MAPPINGS:
|
|
114
|
+
os_family = _USERAGENT_PLATFORM_NAME_MAPPINGS[plt_name_lower]
|
|
115
|
+
else:
|
|
116
|
+
os_family = None
|
|
117
|
+
|
|
118
|
+
if os_family is not None:
|
|
119
|
+
return [UserAgentComponent("os", os_family, self._platform_version)]
|
|
120
|
+
else:
|
|
121
|
+
return [
|
|
122
|
+
UserAgentComponent("os", "other"),
|
|
123
|
+
UserAgentComponent("md", self._platform_name, self._platform_version),
|
|
124
|
+
]
|
|
125
|
+
|
|
126
|
+
def _build_architecture_metadata(self) -> list[UserAgentComponent]:
|
|
127
|
+
"""Build architecture component of the User-Agent header string.
|
|
128
|
+
|
|
129
|
+
Returns the machine type with prefix "md" and name "arch", if one is available.
|
|
130
|
+
Common values include "x86_64", "arm64", "i386".
|
|
131
|
+
"""
|
|
132
|
+
if self._platform_machine:
|
|
133
|
+
return [UserAgentComponent("md", "arch", self._platform_machine.lower())]
|
|
134
|
+
return []
|
|
135
|
+
|
|
136
|
+
def _build_language_metadata(self) -> list[UserAgentComponent]:
|
|
137
|
+
"""Build the language components of the User-Agent header string.
|
|
138
|
+
|
|
139
|
+
Returns the Python version in a component with prefix "lang" and name
|
|
140
|
+
"python". The Python implementation (e.g. CPython, PyPy) is returned as
|
|
141
|
+
separate metadata component with prefix "md" and name "pyimpl".
|
|
142
|
+
|
|
143
|
+
String representation of an example return value:
|
|
144
|
+
``lang/python#3.10.4 md/pyimpl#CPython``
|
|
145
|
+
"""
|
|
146
|
+
lang_md = [
|
|
147
|
+
UserAgentComponent("lang", "python", self._python_version),
|
|
148
|
+
]
|
|
149
|
+
if self._python_implementation:
|
|
150
|
+
lang_md.append(
|
|
151
|
+
UserAgentComponent("md", "pyimpl", self._python_implementation)
|
|
152
|
+
)
|
|
153
|
+
return lang_md
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
from collections.abc import Iterator
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from enum import Enum
|
|
6
|
+
from typing import Protocol
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class FieldPosition(Enum):
|
|
10
|
+
"""The type of a field.
|
|
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.
|
|
24
|
+
|
|
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
|
+
"""
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class Field(Protocol):
|
|
31
|
+
"""A name-value pair representing a single field in a request or response.
|
|
32
|
+
|
|
33
|
+
The kind will dictate metadata placement within an the message, for example as
|
|
34
|
+
header or trailer field in a HTTP request as defined in RFC 9110 Section 5.
|
|
35
|
+
|
|
36
|
+
All field names are case insensitive and case-variance must be treated as
|
|
37
|
+
equivalent. Names may be normalized but should be preserved for accuracy during
|
|
38
|
+
transmission.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
name: str
|
|
42
|
+
values: list[str]
|
|
43
|
+
kind: FieldPosition = FieldPosition.HEADER
|
|
44
|
+
|
|
45
|
+
def add(self, value: str) -> None:
|
|
46
|
+
"""Append a value to a field."""
|
|
47
|
+
...
|
|
48
|
+
|
|
49
|
+
def set(self, values: list[str]) -> None:
|
|
50
|
+
"""Overwrite existing field values."""
|
|
51
|
+
...
|
|
52
|
+
|
|
53
|
+
def remove(self, value: str) -> None:
|
|
54
|
+
"""Remove all matching entries from list."""
|
|
55
|
+
...
|
|
56
|
+
|
|
57
|
+
def as_string(self) -> str:
|
|
58
|
+
"""Serialize the ``Field``'s values into a single line string."""
|
|
59
|
+
...
|
|
60
|
+
|
|
61
|
+
def as_tuples(self) -> list[tuple[str, str]]:
|
|
62
|
+
"""Get list of ``name``, ``value`` tuples where each tuple represents one
|
|
63
|
+
value."""
|
|
64
|
+
...
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class Fields(Protocol):
|
|
68
|
+
"""Protocol agnostic mapping of key-value pair request metadata, such as HTTP
|
|
69
|
+
fields."""
|
|
70
|
+
|
|
71
|
+
# Entries are keyed off the name of a provided Field
|
|
72
|
+
entries: dict[str, Field]
|
|
73
|
+
encoding: str = "utf-8"
|
|
74
|
+
|
|
75
|
+
def set_field(self, field: Field) -> None:
|
|
76
|
+
"""Alias for __setitem__ to utilize the field.name for the entry key."""
|
|
77
|
+
...
|
|
78
|
+
|
|
79
|
+
def __setitem__(self, name: str, field: Field) -> None:
|
|
80
|
+
"""Set entry for a Field name."""
|
|
81
|
+
...
|
|
82
|
+
|
|
83
|
+
def __getitem__(self, name: str) -> Field:
|
|
84
|
+
"""Retrieve Field entry."""
|
|
85
|
+
...
|
|
86
|
+
|
|
87
|
+
def __delitem__(self, name: str) -> None:
|
|
88
|
+
"""Delete entry from collection."""
|
|
89
|
+
...
|
|
90
|
+
|
|
91
|
+
def __iter__(self) -> Iterator[Field]:
|
|
92
|
+
"""Allow iteration over entries."""
|
|
93
|
+
...
|
|
94
|
+
|
|
95
|
+
def __len__(self) -> int:
|
|
96
|
+
"""Get total number of Field entries."""
|
|
97
|
+
...
|
|
98
|
+
|
|
99
|
+
def __contains__(self, key: str) -> bool:
|
|
100
|
+
"""Allows in/not in checks on Field entries."""
|
|
101
|
+
...
|
|
102
|
+
|
|
103
|
+
def get_by_type(self, kind: FieldPosition) -> list[Field]:
|
|
104
|
+
"""Helper function for retrieving specific types of fields.
|
|
105
|
+
|
|
106
|
+
Used to grab all headers or all trailers.
|
|
107
|
+
"""
|
|
108
|
+
...
|
|
109
|
+
|
|
110
|
+
def extend(self, other: "Fields") -> None:
|
|
111
|
+
"""Merges ``entries`` of ``other`` into the current ``entries``.
|
|
112
|
+
|
|
113
|
+
For every `Field` in the ``entries`` of ``other``: If the normalized name
|
|
114
|
+
already exists in the current ``entries``, the values from ``other`` are
|
|
115
|
+
appended. Otherwise, the ``Field`` is added to the list of ``entries``.
|
|
116
|
+
"""
|
|
117
|
+
...
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
QueryParamsList = list[tuple[str, str]]
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
@dataclass(kw_only=True)
|
|
124
|
+
class HTTPClientConfiguration:
|
|
125
|
+
"""Client-level HTTP configuration.
|
|
126
|
+
|
|
127
|
+
:param force_http_2: Whether to require HTTP/2.
|
|
128
|
+
"""
|
|
129
|
+
|
|
130
|
+
force_http_2: bool = False
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
@dataclass(kw_only=True)
|
|
134
|
+
class HTTPRequestConfiguration:
|
|
135
|
+
"""Request-level HTTP configuration.
|
|
136
|
+
|
|
137
|
+
:param read_timeout: How long, in seconds, the client will attempt to read the first
|
|
138
|
+
byte over an established, open connection before timing out.
|
|
139
|
+
"""
|
|
140
|
+
|
|
141
|
+
read_timeout: float | None = None
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
from typing import Any, Protocol
|
|
4
|
+
|
|
5
|
+
from smithy_core.interceptors import Interceptor
|
|
6
|
+
|
|
7
|
+
from smithy_http.interceptors.user_agent import UserAgentInterceptor
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class _InterceptorConfig(Protocol):
|
|
11
|
+
interceptors: list[Interceptor[Any, Any, Any, Any]]
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def user_agent_plugin(config: _InterceptorConfig) -> None:
|
|
15
|
+
config.interceptors.append(UserAgentInterceptor())
|
smithy_http/py.typed
ADDED
|
File without changes
|
smithy_http/restjson.py
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
from typing import NamedTuple
|
|
4
|
+
|
|
5
|
+
from smithy_core.documents import DocumentValue
|
|
6
|
+
|
|
7
|
+
_REST_JSON_CODE_HEADER = "x-amzn-errortype"
|
|
8
|
+
|
|
9
|
+
_REST_JSON_CODE_KEYS = {"__type", "code"}
|
|
10
|
+
|
|
11
|
+
_REST_JSON_MESSAGE_KEYS = {"message", "errormessage", "error_message"}
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class RestJsonErrorInfo(NamedTuple):
|
|
15
|
+
"""Generic error information from a RestJson protocol error."""
|
|
16
|
+
|
|
17
|
+
code: str
|
|
18
|
+
"""The error code."""
|
|
19
|
+
|
|
20
|
+
message: str
|
|
21
|
+
"""The generic error message.
|
|
22
|
+
|
|
23
|
+
A modeled error may have the error bound somewhere else. This is based off of
|
|
24
|
+
checking the most common locations and is intended for use with excpetions that
|
|
25
|
+
either didn't model the message or which are unknown.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
json_body: dict[str, DocumentValue] | None = None
|
|
29
|
+
"""The HTTP response body parsed as JSON."""
|