jararaca 0.3.11a14__py3-none-any.whl → 0.3.11a16__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.
- jararaca/cli.py +280 -110
- jararaca/messagebus/worker.py +861 -89
- jararaca/utils/retry.py +141 -0
- {jararaca-0.3.11a14.dist-info → jararaca-0.3.11a16.dist-info}/METADATA +1 -1
- {jararaca-0.3.11a14.dist-info → jararaca-0.3.11a16.dist-info}/RECORD +8 -7
- {jararaca-0.3.11a14.dist-info → jararaca-0.3.11a16.dist-info}/LICENSE +0 -0
- {jararaca-0.3.11a14.dist-info → jararaca-0.3.11a16.dist-info}/WHEEL +0 -0
- {jararaca-0.3.11a14.dist-info → jararaca-0.3.11a16.dist-info}/entry_points.txt +0 -0
jararaca/utils/retry.py
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import logging
|
|
3
|
+
import random
|
|
4
|
+
from functools import wraps
|
|
5
|
+
from typing import Awaitable, Callable, Optional, ParamSpec, TypeVar
|
|
6
|
+
|
|
7
|
+
logger = logging.getLogger(__name__)
|
|
8
|
+
|
|
9
|
+
P = ParamSpec("P")
|
|
10
|
+
T = TypeVar("T")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class RetryConfig:
|
|
14
|
+
"""Configuration for the retry mechanism."""
|
|
15
|
+
|
|
16
|
+
def __init__(
|
|
17
|
+
self,
|
|
18
|
+
max_retries: int = 5,
|
|
19
|
+
initial_delay: float = 1.0,
|
|
20
|
+
max_delay: float = 60.0,
|
|
21
|
+
backoff_factor: float = 2.0,
|
|
22
|
+
jitter: bool = True,
|
|
23
|
+
):
|
|
24
|
+
"""
|
|
25
|
+
Initialize retry configuration.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
max_retries: Maximum number of retry attempts (default: 5)
|
|
29
|
+
initial_delay: Initial delay in seconds between retries (default: 1.0)
|
|
30
|
+
max_delay: Maximum delay in seconds between retries (default: 60.0)
|
|
31
|
+
backoff_factor: Multiplier for the delay after each retry (default: 2.0)
|
|
32
|
+
jitter: Whether to add randomness to the delay to prevent thundering herd (default: True)
|
|
33
|
+
"""
|
|
34
|
+
self.max_retries = max_retries
|
|
35
|
+
self.initial_delay = initial_delay
|
|
36
|
+
self.max_delay = max_delay
|
|
37
|
+
self.backoff_factor = backoff_factor
|
|
38
|
+
self.jitter = jitter
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
E = TypeVar("E", bound=Exception)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
async def retry_with_backoff(
|
|
45
|
+
fn: Callable[[], Awaitable[T]],
|
|
46
|
+
# args: P.args,
|
|
47
|
+
# kwargs: P.kwargs,
|
|
48
|
+
retry_config: Optional[RetryConfig] = None,
|
|
49
|
+
on_retry_callback: Optional[Callable[[int, E, float], None]] = None,
|
|
50
|
+
retry_exceptions: tuple[type[E], ...] = (),
|
|
51
|
+
) -> T:
|
|
52
|
+
"""
|
|
53
|
+
Execute a function with exponential backoff retry mechanism.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
fn: The async function to execute with retry
|
|
57
|
+
*args: Arguments to pass to the function
|
|
58
|
+
retry_config: Configuration for the retry mechanism
|
|
59
|
+
on_retry_callback: Optional callback function called on each retry with retry count, exception, and next delay
|
|
60
|
+
retry_exceptions: Tuple of exception types that should trigger a retry
|
|
61
|
+
**kwargs: Keyword arguments to pass to the function
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
The result of the function if successful
|
|
65
|
+
|
|
66
|
+
Raises:
|
|
67
|
+
The last exception encountered if all retries fail
|
|
68
|
+
"""
|
|
69
|
+
if retry_config is None:
|
|
70
|
+
retry_config = RetryConfig()
|
|
71
|
+
|
|
72
|
+
last_exception = None
|
|
73
|
+
delay = retry_config.initial_delay
|
|
74
|
+
|
|
75
|
+
for retry_count in range(retry_config.max_retries + 1):
|
|
76
|
+
try:
|
|
77
|
+
return await fn()
|
|
78
|
+
except retry_exceptions as e:
|
|
79
|
+
last_exception = e
|
|
80
|
+
|
|
81
|
+
if retry_count >= retry_config.max_retries:
|
|
82
|
+
logger.error(
|
|
83
|
+
f"Max retries ({retry_config.max_retries}) exceeded: {str(e)}"
|
|
84
|
+
)
|
|
85
|
+
raise
|
|
86
|
+
|
|
87
|
+
# Calculate next delay with exponential backoff
|
|
88
|
+
if retry_count > 0: # Don't increase delay on the first failure
|
|
89
|
+
delay = min(delay * retry_config.backoff_factor, retry_config.max_delay)
|
|
90
|
+
|
|
91
|
+
# Apply jitter if configured (±25% randomness)
|
|
92
|
+
if retry_config.jitter:
|
|
93
|
+
jitter_amount = delay * 0.25
|
|
94
|
+
delay = delay + random.uniform(-jitter_amount, jitter_amount)
|
|
95
|
+
# Ensure delay doesn't go negative due to jitter
|
|
96
|
+
delay = max(delay, 0.1)
|
|
97
|
+
|
|
98
|
+
logger.warning(
|
|
99
|
+
f"Retry {retry_count+1}/{retry_config.max_retries} after error: {str(e)}. "
|
|
100
|
+
f"Retrying in {delay:.2f}s"
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
# Call the optional retry callback if provided
|
|
104
|
+
if on_retry_callback:
|
|
105
|
+
on_retry_callback(retry_count, e, delay)
|
|
106
|
+
|
|
107
|
+
await asyncio.sleep(delay)
|
|
108
|
+
|
|
109
|
+
# This should never be reached with the current implementation
|
|
110
|
+
if last_exception:
|
|
111
|
+
raise last_exception
|
|
112
|
+
raise RuntimeError("Unexpected error in retry logic")
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def with_retry(
|
|
116
|
+
retry_config: Optional[RetryConfig] = None,
|
|
117
|
+
retry_exceptions: tuple[type[Exception], ...] = (Exception,),
|
|
118
|
+
) -> Callable[[Callable[P, Awaitable[T]]], Callable[P, Awaitable[T]]]:
|
|
119
|
+
"""
|
|
120
|
+
Decorator to wrap an async function with retry logic.
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
retry_config: Configuration for the retry mechanism
|
|
124
|
+
retry_exceptions: Tuple of exception types that should trigger a retry
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
Decorated function with retry mechanism
|
|
128
|
+
"""
|
|
129
|
+
|
|
130
|
+
def decorator(fn: Callable[P, Awaitable[T]]) -> Callable[P, Awaitable[T]]:
|
|
131
|
+
@wraps(fn)
|
|
132
|
+
async def wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
|
|
133
|
+
return await retry_with_backoff(
|
|
134
|
+
lambda: fn(*args, **kwargs),
|
|
135
|
+
retry_config=retry_config,
|
|
136
|
+
retry_exceptions=retry_exceptions,
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
return wrapper
|
|
140
|
+
|
|
141
|
+
return decorator
|
|
@@ -3,7 +3,7 @@ jararaca/__main__.py,sha256=-O3vsB5lHdqNFjUtoELDF81IYFtR-DSiiFMzRaiSsv4,67
|
|
|
3
3
|
jararaca/broker_backend/__init__.py,sha256=GzEIuHR1xzgCJD4FE3harNjoaYzxHMHoEL0_clUaC-k,3528
|
|
4
4
|
jararaca/broker_backend/mapper.py,sha256=vTsi7sWpNvlga1PWPFg0rCJ5joJ0cdzykkIc2Tuvenc,696
|
|
5
5
|
jararaca/broker_backend/redis_broker_backend.py,sha256=a7DHchy3NAiD71Ix8SwmQOUnniu7uup-Woa4ON_4J7I,5786
|
|
6
|
-
jararaca/cli.py,sha256=
|
|
6
|
+
jararaca/cli.py,sha256=zkWRcqllY_C0sIR7h_crlptq2cA6sxRM4nvMMLBaANs,25946
|
|
7
7
|
jararaca/common/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
8
|
jararaca/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
9
|
jararaca/core/providers.py,sha256=wktH84FK7c1s2wNq-fudf1uMfi3CQBR0neU2czJ_L0U,434
|
|
@@ -20,7 +20,7 @@ jararaca/messagebus/interceptors/aiopika_publisher_interceptor.py,sha256=_DEHwIH
|
|
|
20
20
|
jararaca/messagebus/interceptors/publisher_interceptor.py,sha256=ojy1bRhqMgrkQljcGGS8cd8-8pUjL8ZHjIUkdmaAnNM,1325
|
|
21
21
|
jararaca/messagebus/message.py,sha256=U6cyd2XknX8mtm0333slz5fanky2PFLWCmokAO56vvU,819
|
|
22
22
|
jararaca/messagebus/publisher.py,sha256=JTkxdKbvxvDWT8nK8PVEyyX061vYYbKQMxRHXrZtcEY,2173
|
|
23
|
-
jararaca/messagebus/worker.py,sha256=
|
|
23
|
+
jararaca/messagebus/worker.py,sha256=CrSIejWMGII4_JK0aH4jxdj0oBJX4hSXY0SmVa6KURA,54187
|
|
24
24
|
jararaca/microservice.py,sha256=rRIimfeP2-wf289PKoUbk9wrSdA0ga_qWz5JNgQ5IE0,9667
|
|
25
25
|
jararaca/observability/decorators.py,sha256=MOIr2PttPYYvRwEdfQZEwD5RxKHOTv8UEy9n1YQVoKw,2281
|
|
26
26
|
jararaca/observability/interceptor.py,sha256=U4ZLM0f8j6Q7gMUKKnA85bnvD-Qa0ii79Qa_X8KsXAQ,1498
|
|
@@ -66,8 +66,9 @@ jararaca/tools/app_config/interceptor.py,sha256=HV8h4AxqUc_ACs5do4BSVlyxlRXzx7Hq
|
|
|
66
66
|
jararaca/tools/typescript/interface_parser.py,sha256=35xbOrZDQDyTXdMrVZQ8nnFw79f28lJuLYNHAspIqi8,30492
|
|
67
67
|
jararaca/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
68
68
|
jararaca/utils/rabbitmq_utils.py,sha256=ytdAFUyv-OBkaVnxezuJaJoLrmN7giZgtKeet_IsMBs,10918
|
|
69
|
-
jararaca
|
|
70
|
-
jararaca-0.3.
|
|
71
|
-
jararaca-0.3.
|
|
72
|
-
jararaca-0.3.
|
|
73
|
-
jararaca-0.3.
|
|
69
|
+
jararaca/utils/retry.py,sha256=DzPX_fXUvTqej6BQ8Mt2dvLo9nNlTBm7Kx2pFZ26P2Q,4668
|
|
70
|
+
jararaca-0.3.11a16.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
71
|
+
jararaca-0.3.11a16.dist-info/METADATA,sha256=E5OUx4jCVKdki-auNqJUVcOflGaKTt37W3FAtKYS7ow,4998
|
|
72
|
+
jararaca-0.3.11a16.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
|
73
|
+
jararaca-0.3.11a16.dist-info/entry_points.txt,sha256=WIh3aIvz8LwUJZIDfs4EeH3VoFyCGEk7cWJurW38q0I,45
|
|
74
|
+
jararaca-0.3.11a16.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|