Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ private void generateService(PythonWriter writer) {
}

writer.addDependency(SmithyPythonDependency.SMITHY_CORE);
writer.addImport("smithy_core.retries", "RetryStrategyResolver");
writer.addImport("smithy_core.aio.retries", "RetryStrategyResolver");
writer.write("""
def __init__(self, config: $1T | None = None, plugins: list[$2T] | None = None):
$3C
Expand Down Expand Up @@ -215,7 +215,7 @@ private void writeSharedOperationInit(
writer.addImport("smithy_core.aio.client", "RequestPipeline");
writer.addImport("smithy_core.exceptions", "ExpectationNotMetError");
writer.addImport("smithy_core.retries", "RetryStrategyOptions");
writer.addImport("smithy_core.interfaces.retries", "RetryStrategy");
writer.addImport("smithy_core.aio.interfaces.retries", "RetryStrategy");
writer.addStdlibImport("copy", "deepcopy");

writer.write("""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ private void generateRequestTest(OperationShape operation, HttpRequestTestCase t
} else {
path = "";
}
writer.addImport("smithy_core.retries", "SimpleRetryStrategy");
writer.addImport("smithy_core.aio.retries", "SimpleRetryStrategy");
writeClientBlock(context.symbolProvider().toSymbol(service), testCase, Optional.of(() -> {
writer.write("""
config = $T(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ public final class ConfigGenerator implements Runnable {
.name("RetryStrategy | RetryStrategyOptions")
.addReference(Symbol.builder()
.name("RetryStrategy")
.namespace("smithy_core.interfaces.retries", ".")
.namespace("smithy_core.aio.interfaces.retries", ".")
.addDependency(SmithyPythonDependency.SMITHY_CORE)
.build())
.addReference(Symbol.builder()
Expand Down
12 changes: 6 additions & 6 deletions designs/retries.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ class RetryStrategy(Protocol):
max_attempts: int
"""Upper limit on total attempt count (initial attempt plus retries)."""

def acquire_initial_retry_token(
async def acquire_initial_retry_token(
self, *, token_scope: str | None = None
) -> RetryToken:
"""Called before any retries (for the first attempt at the operation).
Expand All @@ -44,7 +44,7 @@ class RetryStrategy(Protocol):
"""
...

def refresh_retry_token_for_retry(
async def refresh_retry_token_for_retry(
self, *, token_to_renew: RetryToken, error: Exception
) -> RetryToken:
"""Replace an existing retry token from a failed attempt with a new token.
Expand All @@ -55,7 +55,7 @@ class RetryStrategy(Protocol):
"""
...

def record_success(self, *, token: RetryToken) -> None:
async def record_success(self, *, token: RetryToken) -> None:
"""Return token after successful completion of an operation.

:param token: The token used for the previous successful attempt.
Expand Down Expand Up @@ -144,7 +144,7 @@ example:

```python
try:
retry_token = retry_strategy.acquire_initial_retry_token()
retry_token = await retry_strategy.acquire_initial_retry_token()
except RetryError:
transport_response = transport_client.send(serialized_request)
return self._deserialize(transport_response)
Expand All @@ -159,14 +159,14 @@ while True:

if isinstance(response, Exception):
try:
retry_token = retry_strategy.refresh_retry_token_for_retry(
retry_token = await retry_strategy.refresh_retry_token_for_retry(
token_to_renew=retry_token,
error=e
)
continue
except RetryError as retry_error:
raise retry_error from e

retry_strategy.record_success(token=retry_token)
await retry_strategy.record_success(token=retry_token)
return response
```
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@

from smithy_core import URI
from smithy_core.aio.interfaces.identity import IdentityResolver
from smithy_core.aio.interfaces.retries import RetryStrategy
from smithy_core.aio.retries import SimpleRetryStrategy
from smithy_core.exceptions import SmithyIdentityError
from smithy_core.interfaces.retries import RetryStrategy
from smithy_core.retries import SimpleRetryStrategy
from smithy_http import Field, Fields
from smithy_http.aio import HTTPRequest
from smithy_http.aio.interfaces import HTTPClient
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
TokenCache,
)
from smithy_core import URI
from smithy_core.retries import SimpleRetryStrategy
from smithy_core.aio.retries import SimpleRetryStrategy
from smithy_http.aio import HTTPRequest


Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"type": "breaking",
"description": "Refactored retry strategies to be async, allowing them to wait internally or use async synchronization primitives if necessary."
}
8 changes: 4 additions & 4 deletions packages/smithy-core/src/smithy_core/aio/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
)
from ..interfaces import Endpoint, TypedProperties
from ..interfaces.auth import AuthOption, AuthSchemeResolver
from ..interfaces.retries import RetryStrategy
from ..schemas import APIOperation
from ..serializers import SerializeableShape
from ..shapes import ShapeID
Expand All @@ -37,6 +36,7 @@
)
from .interfaces.auth import AuthScheme
from .interfaces.eventstream import EventReceiver
from .interfaces.retries import RetryStrategy
from .utils import seek

if TYPE_CHECKING:
Expand Down Expand Up @@ -330,7 +330,7 @@ async def _retry[I: SerializeableShape, O: DeserializeableShape](
return await self._handle_attempt(call, request_context, request_future)

retry_strategy = call.retry_strategy
retry_token = retry_strategy.acquire_initial_retry_token(
retry_token = await retry_strategy.acquire_initial_retry_token(
token_scope=call.retry_scope
)

Expand All @@ -349,7 +349,7 @@ async def _retry[I: SerializeableShape, O: DeserializeableShape](

if isinstance(output_context.response, Exception):
try:
retry_token = retry_strategy.refresh_retry_token_for_retry(
retry_token = await retry_strategy.refresh_retry_token_for_retry(
token_to_renew=retry_token,
error=output_context.response,
)
Expand All @@ -364,7 +364,7 @@ async def _retry[I: SerializeableShape, O: DeserializeableShape](

await seek(request_context.transport_request.body, 0)
else:
retry_strategy.record_success(token=retry_token)
await retry_strategy.record_success(token=retry_token)
return output_context

async def _handle_attempt[I: SerializeableShape, O: DeserializeableShape](
Expand Down
56 changes: 56 additions & 0 deletions packages/smithy-core/src/smithy_core/aio/interfaces/retries.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: Apache-2.0
from typing import Protocol, runtime_checkable

from ...interfaces.retries import RetryBackoffStrategy, RetryToken


@runtime_checkable
class RetryStrategy(Protocol):
"""Issuer of :py:class:`RetryToken`s."""

backoff_strategy: RetryBackoffStrategy
"""The strategy used by returned tokens to compute delay duration values."""

max_attempts: int
"""Upper limit on total attempt count (initial attempt plus retries)."""

async def acquire_initial_retry_token(
self, *, token_scope: str | None = None
) -> RetryToken:
"""Create a base retry token for the start of a request.

:param token_scope: An arbitrary string accepted by the retry strategy to
separate tokens into scopes.
:returns: A retry token, to be used for determining the retry delay, refreshing
the token after a failure, and recording success after success.
:raises RetryError: If the retry strategy has no available tokens.
"""
...

async def refresh_retry_token_for_retry(
self, *, token_to_renew: RetryToken, error: Exception
) -> RetryToken:
"""Replace an existing retry token from a failed attempt with a new token.

After a failed operation call, this method is called to exchange a retry token
that was previously obtained by calling :py:func:`acquire_initial_retry_token`
or this method with a new retry token for the next attempt. This method can
either choose to allow another retry and send a new or updated token, or reject
the retry attempt and raise the error.

:param token_to_renew: The token used for the previous failed attempt.
:param error: The error that triggered the need for a retry.
:raises RetryError: If no further retry attempts are allowed.
"""
...

async def record_success(self, *, token: RetryToken) -> None:
"""Return token after successful completion of an operation.

Upon successful completion of the operation, a user calls this function to
record that the operation was successful.

:param token: The token used for the previous successful attempt.
"""
...
Loading
Loading