Skip to content

Commit db1d84a

Browse files
Add client protocol interface
1 parent 97571c7 commit db1d84a

File tree

4 files changed

+154
-5
lines changed

4 files changed

+154
-5
lines changed

packages/smithy-core/src/smithy_core/aio/interfaces/__init__.py

Lines changed: 73 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,19 @@
11
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
22
# SPDX-License-Identifier: Apache-2.0
33
from collections.abc import AsyncIterable
4-
from typing import Protocol, runtime_checkable
4+
from typing import Protocol, runtime_checkable, TYPE_CHECKING, Any
55

6-
from ...interfaces import URI
6+
from ...interfaces import URI, Endpoint
77
from ...interfaces import StreamingBlob as SyncStreamingBlob
88

99

10+
if TYPE_CHECKING:
11+
from ...schemas import APIOperation
12+
from ...shapes import ShapeID
13+
from ...serializers import SerializeableShape
14+
from ...deserializers import DeserializeableShape
15+
16+
1017
@runtime_checkable
1118
class AsyncByteStream(Protocol):
1219
"""A file-like object with an async read method."""
@@ -67,3 +74,67 @@ class ClientTransport[I: Request, O: Response](Protocol):
6774
async def send(self, request: I) -> O:
6875
"""Send a request over the transport and receive the response."""
6976
...
77+
78+
79+
class ClientProtocol[I: Request, O: Response](Protocol):
80+
"""A protocol used by a client to communicate with a server."""
81+
82+
@property
83+
def id(self) -> "ShapeID":
84+
"""The ID of the protocol."""
85+
...
86+
87+
def serialize_request[
88+
OperationInput: "SerializeableShape",
89+
OperationOutput: "DeserializeableShape",
90+
](
91+
self,
92+
*,
93+
operation: APIOperation[OperationInput, OperationOutput],
94+
input: OperationInput,
95+
endpoint: URI,
96+
context: dict[str, Any],
97+
) -> I:
98+
"""Serialize an operation input into a transport request.
99+
100+
:param operation: The operation whose request is being serialized.
101+
:param input: The input shape to be serialized.
102+
:param endpoint: The base endpoint to serialize.
103+
:param context: A context bag for the request.
104+
"""
105+
...
106+
107+
def set_service_endpoint(
108+
self,
109+
*,
110+
request: I,
111+
endpoint: Endpoint,
112+
) -> I:
113+
"""Update the endpoint of a transport request.
114+
115+
:param request: The request whose endpoint should be updated.
116+
:param endpoint: The endpoint to set on the request.
117+
"""
118+
...
119+
120+
async def deserialize_response[
121+
OperationInput: "SerializeableShape",
122+
OperationOutput: "DeserializeableShape",
123+
](
124+
self,
125+
*,
126+
operation: APIOperation[OperationInput, OperationOutput],
127+
request: I,
128+
response: O,
129+
error_registry: Any, # TODO: add error registry
130+
context: dict[str, Any], # TODO: replace with a typed context bag
131+
) -> OperationOutput:
132+
"""Deserializes the output from the tranport response or throws an exception.
133+
134+
:param operation: The operation whose response is being deserialized.
135+
:param request: The transport request that was sent for this response.
136+
:param response: The response to deserialize.
137+
:param error_registry: A TypeRegistry used to deserialize errors.
138+
:param context: A context bag for the request.
139+
"""
140+
...

packages/smithy-core/src/smithy_core/interfaces/__init__.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,3 +83,19 @@ def is_streaming_blob(obj: Any) -> TypeGuard[StreamingBlob]:
8383
:param obj: The object to inspect.
8484
"""
8585
return isinstance(obj, bytes | bytearray) or is_bytes_reader(obj)
86+
87+
88+
# TODO: update HTTP package and existing endpoint implementations to use this.
89+
class Endpoint(Protocol):
90+
"""A resolved endpoint."""
91+
92+
uri: URI
93+
"""The endpoint URI."""
94+
95+
# TODO: replace this with a typed context bag
96+
properties: dict[str, Any]
97+
"""Properties required to interact with the endpoint.
98+
99+
For example, in some AWS use cases this might contain HTTP headers to add to each
100+
request.
101+
"""

packages/smithy-core/src/smithy_core/schemas.py

Lines changed: 59 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
22
# SPDX-License-Identifier: Apache-2.0
3-
from collections.abc import Mapping
3+
from collections.abc import Mapping, Sequence
44
from dataclasses import dataclass, field, replace
5-
from typing import NotRequired, Required, Self, TypedDict, overload, Any
5+
from typing import NotRequired, Required, Self, TypedDict, overload, Any, TYPE_CHECKING
66

77
from .exceptions import ExpectationNotMetException, SmithyException
88
from .shapes import ShapeID, ShapeType
9-
from .traits import Trait, DynamicTrait
9+
from .traits import Trait, DynamicTrait, IdempotencyTokenTrait, StreamingTrait
10+
11+
12+
if TYPE_CHECKING:
13+
from .serializers import SerializeableShape
14+
from .deserializers import DeserializeableShape
1015

1116

1217
@dataclass(kw_only=True, frozen=True, init=False)
@@ -263,3 +268,54 @@ class MemberSchema(TypedDict):
263268
target: Required[Schema]
264269
index: Required[int]
265270
traits: NotRequired[list["Trait | DynamicTrait"]]
271+
272+
273+
@dataclass(kw_only=True, frozen=True)
274+
class APIOperation[I: "SerializeableShape", O: "DeserializeableShape"]:
275+
"""A modeled Smithy operation."""
276+
277+
input: type[I]
278+
"""The input type of the operation."""
279+
280+
output: type[O]
281+
"""The output type of the operation."""
282+
283+
schema: Schema
284+
"""The schema of the operation."""
285+
286+
input_schema: Schema
287+
"""The schema of the operation's input shape."""
288+
289+
output_schema: Schema
290+
"""The schema of the operation's output shape."""
291+
292+
# TODO: Add a type registry for errors
293+
error_registry: Any
294+
"""A TypeRegistry used to create errors."""
295+
296+
effective_auth_schemes: Sequence[ShapeID]
297+
"""A list of effective auth schemes for the operation."""
298+
299+
@property
300+
def idempotency_token_member(self) -> Schema | None:
301+
"""The input schema member that serves as the idempotency token."""
302+
for member in self.input_schema.members.values():
303+
if member.get_trait(IdempotencyTokenTrait) is not None:
304+
return member
305+
return None
306+
307+
@property
308+
def input_stream_member(self) -> Schema | None:
309+
"""The input schema member that contains an event stream or data stream."""
310+
for member in self.input_schema.members.values():
311+
if member.get_trait(StreamingTrait) is not None:
312+
return member
313+
return None
314+
315+
@property
316+
def output_stream_member(self) -> Schema | None:
317+
"""The output schema member that contains an event stream or data stream."""
318+
for member in self.output_schema.members.values():
319+
if member.get_trait(StreamingTrait) is not None:
320+
return member
321+
return None

packages/smithy-core/src/smithy_core/traits.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,12 @@ def value(self) -> str:
195195
return self.document_value # type: ignore
196196

197197

198+
@dataclass(init=False, frozen=True)
199+
class IdempotencyTokenTrait(Trait, id=ShapeID("smithy.api#IdempotencyToken")):
200+
def __post_init__(self):
201+
assert self.document_value is None
202+
203+
198204
# TODO: Get all this moved over to the http package
199205
@dataclass(init=False, frozen=True)
200206
class HTTPTrait(Trait, id=ShapeID("smithy.api#http")):

0 commit comments

Comments
 (0)