windlass.core.retry¶
retry
¶
Retry with exponential backoff and jitter.
Provider calls fail transiently: rate limits, 5xx, socket resets. Windlass wraps
every outbound call in :func:retry_async so adapters do not each reinvent a
backoff loop, and so the policy is configurable in one place
(:class:~windlass.core.config.RetryConfig).
Only transient failures are retried. A 401 or a schema validation error is raised immediately — retrying those just wastes a minute before failing anyway.
is_retryable
¶
Decide whether exc is worth another attempt.
The decision order matters: an explicit non-retryable class wins over a
retryable base class, so :class:AuthenticationError never retries even
though it subclasses ProviderError.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
exc
|
BaseException
|
The raised exception. |
required |
extra
|
Iterable[type[BaseException]]
|
Additional exception types to treat as retryable — adapters pass their SDK's transient error classes here. |
()
|
Returns:
| Type | Description |
|---|---|
bool
|
True when a retry is warranted. |
Example
is_retryable(RateLimitError("slow down")) True is_retryable(AuthenticationError("bad key")) False
Source code in src\windlass\core\retry.py
backoff_delays
¶
Return the delay schedule a retry loop would use.
Useful for tests and for logging the policy at startup.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
config
|
RetryConfig | None
|
Policy to expand. Defaults to the configured global policy. |
None
|
Returns:
| Type | Description |
|---|---|
list[float]
|
One delay per retry (so |
Example
backoff_delays(RetryConfig(attempts=4, initial_delay=1, multiplier=2)) [1.0, 2.0, 4.0]
Source code in src\windlass\core\retry.py
retry_async
async
¶
retry_async(
fn: Callable[[], Awaitable[_T]],
*,
config: RetryConfig | None = None,
retry_on: Iterable[type[BaseException]] = (),
on_retry: Callable[[int, BaseException, float], None] | None = None,
description: str = "provider call"
) -> _T
Await fn with retries on transient failures.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
fn
|
Callable[[], Awaitable[_T]]
|
A zero-argument callable returning an awaitable. It is re-invoked on each attempt, so pass a lambda rather than a coroutine object — coroutines cannot be awaited twice. |
required |
config
|
RetryConfig | None
|
Retry policy. Defaults to the global configuration. |
None
|
retry_on
|
Iterable[type[BaseException]]
|
Extra exception types to treat as transient. |
()
|
on_retry
|
Callable[[int, BaseException, float], None] | None
|
Callback invoked as |
None
|
description
|
str
|
Label used in log messages. |
'provider call'
|
Returns:
| Type | Description |
|---|---|
_T
|
Whatever |
Raises:
| Type | Description |
|---|---|
Exception
|
The last failure, once attempts are exhausted or the error is classified as non-retryable. |
Example
import asyncio calls = [] async def flaky(): ... calls.append(1) ... if len(calls) < 2: ... raise ConnectionError("reset") ... return "ok" asyncio.run(retry_async(flaky, config=RetryConfig(attempts=3, initial_delay=0))) 'ok'
Source code in src\windlass\core\retry.py
retry_sync
¶
retry_sync(
fn: Callable[[], _T],
*,
config: RetryConfig | None = None,
retry_on: Iterable[type[BaseException]] = (),
description: str = "provider call"
) -> _T
Blocking counterpart of :func:retry_async.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
fn
|
Callable[[], _T]
|
Zero-argument callable, re-invoked per attempt. |
required |
config
|
RetryConfig | None
|
Retry policy. Defaults to the global configuration. |
None
|
retry_on
|
Iterable[type[BaseException]]
|
Extra transient exception types. |
()
|
description
|
str
|
Label used in log messages. |
'provider call'
|
Returns:
| Type | Description |
|---|---|
_T
|
Whatever |
Raises:
| Type | Description |
|---|---|
Exception
|
The last failure once attempts are exhausted. |
Example
retry_sync(lambda: 7, config=RetryConfig(attempts=1)) 7
Source code in src\windlass\core\retry.py
with_retry
¶
with_retry(
*, config: RetryConfig | None = None, retry_on: Iterable[type[BaseException]] = ()
) -> Callable[[Callable[..., Any]], Callable[..., Any]]
Decorator applying the retry policy to a sync or async function.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
config
|
RetryConfig | None
|
Retry policy. Defaults to the global configuration. |
None
|
retry_on
|
Iterable[type[BaseException]]
|
Extra transient exception types. |
()
|
Returns:
| Type | Description |
|---|---|
Callable[[Callable[..., Any]], Callable[..., Any]]
|
A decorator preserving the wrapped function's signature and sync/async |
Callable[[Callable[..., Any]], Callable[..., Any]]
|
nature. |
Example
@with_retry(config=RetryConfig(attempts=2, initial_delay=0)) ... def ping() -> str: ... return "pong" ping() 'pong'