jararaca 0.3.11a16__py3-none-any.whl → 0.4.0a19__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.
Files changed (96) hide show
  1. README.md +121 -0
  2. jararaca/__init__.py +189 -17
  3. jararaca/__main__.py +4 -0
  4. jararaca/broker_backend/__init__.py +4 -0
  5. jararaca/broker_backend/mapper.py +4 -0
  6. jararaca/broker_backend/redis_broker_backend.py +9 -3
  7. jararaca/cli.py +915 -51
  8. jararaca/common/__init__.py +3 -0
  9. jararaca/core/__init__.py +3 -0
  10. jararaca/core/providers.py +8 -0
  11. jararaca/core/uow.py +41 -7
  12. jararaca/di.py +4 -0
  13. jararaca/files/entity.py.mako +4 -0
  14. jararaca/helpers/__init__.py +3 -0
  15. jararaca/helpers/global_scheduler/__init__.py +3 -0
  16. jararaca/helpers/global_scheduler/config.py +21 -0
  17. jararaca/helpers/global_scheduler/controller.py +42 -0
  18. jararaca/helpers/global_scheduler/registry.py +32 -0
  19. jararaca/lifecycle.py +6 -2
  20. jararaca/messagebus/__init__.py +4 -0
  21. jararaca/messagebus/bus_message_controller.py +4 -0
  22. jararaca/messagebus/consumers/__init__.py +3 -0
  23. jararaca/messagebus/decorators.py +121 -61
  24. jararaca/messagebus/implicit_headers.py +49 -0
  25. jararaca/messagebus/interceptors/__init__.py +3 -0
  26. jararaca/messagebus/interceptors/aiopika_publisher_interceptor.py +62 -11
  27. jararaca/messagebus/interceptors/message_publisher_collector.py +62 -0
  28. jararaca/messagebus/interceptors/publisher_interceptor.py +29 -3
  29. jararaca/messagebus/message.py +4 -0
  30. jararaca/messagebus/publisher.py +6 -0
  31. jararaca/messagebus/worker.py +1002 -459
  32. jararaca/microservice.py +113 -2
  33. jararaca/observability/constants.py +7 -0
  34. jararaca/observability/decorators.py +170 -13
  35. jararaca/observability/fastapi_exception_handler.py +37 -0
  36. jararaca/observability/hooks.py +109 -0
  37. jararaca/observability/interceptor.py +4 -0
  38. jararaca/observability/providers/__init__.py +3 -0
  39. jararaca/observability/providers/otel.py +225 -16
  40. jararaca/persistence/base.py +39 -3
  41. jararaca/persistence/exports.py +4 -0
  42. jararaca/persistence/interceptors/__init__.py +3 -0
  43. jararaca/persistence/interceptors/aiosqa_interceptor.py +86 -73
  44. jararaca/persistence/interceptors/constants.py +5 -0
  45. jararaca/persistence/interceptors/decorators.py +50 -0
  46. jararaca/persistence/session.py +3 -0
  47. jararaca/persistence/sort_filter.py +4 -0
  48. jararaca/persistence/utilities.py +73 -20
  49. jararaca/presentation/__init__.py +3 -0
  50. jararaca/presentation/decorators.py +88 -86
  51. jararaca/presentation/exceptions.py +23 -0
  52. jararaca/presentation/hooks.py +4 -0
  53. jararaca/presentation/http_microservice.py +4 -0
  54. jararaca/presentation/server.py +97 -45
  55. jararaca/presentation/websocket/__init__.py +3 -0
  56. jararaca/presentation/websocket/base_types.py +4 -0
  57. jararaca/presentation/websocket/context.py +4 -0
  58. jararaca/presentation/websocket/decorators.py +8 -41
  59. jararaca/presentation/websocket/redis.py +280 -53
  60. jararaca/presentation/websocket/types.py +4 -0
  61. jararaca/presentation/websocket/websocket_interceptor.py +46 -19
  62. jararaca/reflect/__init__.py +3 -0
  63. jararaca/reflect/controller_inspect.py +16 -10
  64. jararaca/reflect/decorators.py +252 -0
  65. jararaca/reflect/helpers.py +18 -0
  66. jararaca/reflect/metadata.py +34 -25
  67. jararaca/rpc/__init__.py +3 -0
  68. jararaca/rpc/http/__init__.py +101 -0
  69. jararaca/rpc/http/backends/__init__.py +14 -0
  70. jararaca/rpc/http/backends/httpx.py +43 -9
  71. jararaca/rpc/http/backends/otel.py +4 -0
  72. jararaca/rpc/http/decorators.py +380 -115
  73. jararaca/rpc/http/httpx.py +3 -0
  74. jararaca/scheduler/__init__.py +3 -0
  75. jararaca/scheduler/beat_worker.py +521 -105
  76. jararaca/scheduler/decorators.py +15 -22
  77. jararaca/scheduler/types.py +4 -0
  78. jararaca/tools/app_config/__init__.py +3 -0
  79. jararaca/tools/app_config/decorators.py +7 -19
  80. jararaca/tools/app_config/interceptor.py +6 -2
  81. jararaca/tools/typescript/__init__.py +3 -0
  82. jararaca/tools/typescript/decorators.py +120 -0
  83. jararaca/tools/typescript/interface_parser.py +1077 -174
  84. jararaca/utils/__init__.py +3 -0
  85. jararaca/utils/env_parse_utils.py +133 -0
  86. jararaca/utils/rabbitmq_utils.py +112 -39
  87. jararaca/utils/retry.py +19 -14
  88. jararaca-0.4.0a19.dist-info/LICENSE +674 -0
  89. jararaca-0.4.0a19.dist-info/LICENSES/GPL-3.0-or-later.txt +232 -0
  90. {jararaca-0.3.11a16.dist-info → jararaca-0.4.0a19.dist-info}/METADATA +12 -7
  91. jararaca-0.4.0a19.dist-info/RECORD +96 -0
  92. {jararaca-0.3.11a16.dist-info → jararaca-0.4.0a19.dist-info}/WHEEL +1 -1
  93. pyproject.toml +132 -0
  94. jararaca-0.3.11a16.dist-info/RECORD +0 -74
  95. /jararaca-0.3.11a16.dist-info/LICENSE → /LICENSE +0 -0
  96. {jararaca-0.3.11a16.dist-info → jararaca-0.4.0a19.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,3 @@
1
+ # SPDX-FileCopyrightText: 2025 Lucas S
2
+ #
3
+ # SPDX-License-Identifier: GPL-3.0-or-later
@@ -0,0 +1,133 @@
1
+ # SPDX-FileCopyrightText: 2025 Lucas S
2
+ #
3
+ # SPDX-License-Identifier: GPL-3.0-or-later
4
+
5
+ import os
6
+ from typing import Literal, Optional, TypeVar, overload
7
+
8
+
9
+ def is_env_truffy(var_name: str) -> bool:
10
+ value = os.getenv(var_name, "").lower()
11
+ return value in ("1", "true", "yes", "on")
12
+
13
+
14
+ DF_BOOL_T = TypeVar("DF_BOOL_T", bound="bool")
15
+
16
+
17
+ @overload
18
+ def get_env_bool(
19
+ var_name: str, default: DF_BOOL_T
20
+ ) -> DF_BOOL_T | bool | Literal["invalid"]: ...
21
+
22
+
23
+ @overload
24
+ def get_env_bool(
25
+ var_name: str, default: None = None
26
+ ) -> bool | None | Literal["invalid"]: ...
27
+
28
+
29
+ def get_env_bool(
30
+ var_name: str, default: DF_BOOL_T | None = None
31
+ ) -> DF_BOOL_T | bool | Literal["invalid"] | None:
32
+ value = os.getenv(var_name)
33
+ if value is None:
34
+ return default
35
+ value_lower = value.lower()
36
+ if value_lower in ("1", "true", "yes", "on"):
37
+ return True
38
+ elif value_lower in ("0", "false", "no", "off"):
39
+ return False
40
+ else:
41
+ return "invalid"
42
+
43
+
44
+ DF_INT_T = TypeVar("DF_INT_T", bound="int | None | Literal[False]")
45
+
46
+
47
+ @overload
48
+ def get_env_int(var_name: str, default: None = None) -> int | None | Literal[False]: ...
49
+
50
+
51
+ @overload
52
+ def get_env_int(
53
+ var_name: str, default: DF_INT_T
54
+ ) -> DF_INT_T | int | Literal[False]: ...
55
+
56
+
57
+ def get_env_int(
58
+ var_name: str, default: DF_INT_T = False # type: ignore[assignment]
59
+ ) -> DF_INT_T | int | Literal[False]:
60
+ value = os.getenv(var_name)
61
+ if value is None:
62
+ return default
63
+ try:
64
+ return int(value)
65
+ except ValueError:
66
+ return False
67
+
68
+
69
+ DF_FLOAT_T = TypeVar("DF_FLOAT_T", bound="float | None | Literal[False]")
70
+
71
+
72
+ @overload
73
+ def get_env_float(
74
+ var_name: str, default: None = None
75
+ ) -> float | None | Literal[False]: ...
76
+
77
+
78
+ @overload
79
+ def get_env_float(
80
+ var_name: str, default: DF_FLOAT_T
81
+ ) -> DF_FLOAT_T | float | Literal[False]: ...
82
+
83
+
84
+ def get_env_float(
85
+ var_name: str, default: DF_FLOAT_T = False # type: ignore[assignment]
86
+ ) -> DF_FLOAT_T | float | Literal[False]:
87
+ value = os.getenv(var_name)
88
+ if value is None:
89
+ return default
90
+ try:
91
+ return float(value)
92
+ except ValueError:
93
+ return False
94
+
95
+
96
+ DF_STR_T = TypeVar("DF_STR_T", bound="Optional[str]")
97
+
98
+
99
+ @overload
100
+ def get_env_str(var_name: str, default: None = None) -> str | None: ...
101
+
102
+
103
+ @overload
104
+ def get_env_str(var_name: str, default: DF_STR_T) -> DF_STR_T | str: ...
105
+
106
+
107
+ def get_env_str(var_name: str, default: DF_STR_T = None) -> DF_STR_T | str: # type: ignore[assignment]
108
+ value = os.getenv(var_name)
109
+ if value is None:
110
+ return default
111
+ return value
112
+
113
+
114
+ def get_env_list(var_name: str, separator: str = ",") -> list[str]:
115
+ value = os.getenv(var_name, "")
116
+ if not value:
117
+ return []
118
+ return [item.strip() for item in value.split(separator) if item.strip()]
119
+
120
+
121
+ def get_env_dict(
122
+ var_name: str, item_separator: str = ",", key_value_separator: str = "="
123
+ ) -> dict[str, str]:
124
+ value = os.getenv(var_name, "")
125
+ result: dict[str, str] = {}
126
+ if not value:
127
+ return result
128
+ items = [item.strip() for item in value.split(item_separator) if item.strip()]
129
+ for item in items:
130
+ if key_value_separator in item:
131
+ key, val = item.split(key_value_separator, 1)
132
+ result[key.strip()] = val.strip()
133
+ return result
@@ -1,3 +1,7 @@
1
+ # SPDX-FileCopyrightText: 2025 Lucas S
2
+ #
3
+ # SPDX-License-Identifier: GPL-3.0-or-later
4
+
1
5
  import logging
2
6
 
3
7
  from aio_pika.abc import AbstractChannel, AbstractExchange, AbstractQueue
@@ -27,20 +31,24 @@ class RabbitmqUtils:
27
31
  )
28
32
  except ChannelNotFoundEntity as e:
29
33
  logger.error(
30
- f"Dead Letter Exchange '{cls.DEAD_LETTER_EXCHANGE}' does not exist. "
31
- f"Please use the declare command to create it first. Error: {e}"
34
+ "Dead Letter Exchange '%s' does not exist. "
35
+ "Please use the declare command to create it first. Error: %s",
36
+ cls.DEAD_LETTER_EXCHANGE,
37
+ e,
32
38
  )
33
39
  raise
34
40
  except ChannelClosed as e:
35
41
  logger.error(
36
- f"Channel closed while getting Dead Letter Exchange '{cls.DEAD_LETTER_EXCHANGE}'. "
37
- f"Error: {e}"
42
+ "Channel closed while getting Dead Letter Exchange '%s'. " "Error: %s",
43
+ cls.DEAD_LETTER_EXCHANGE,
44
+ e,
38
45
  )
39
46
  raise
40
47
  except AMQPError as e:
41
48
  logger.error(
42
- f"AMQP error while getting Dead Letter Exchange '{cls.DEAD_LETTER_EXCHANGE}'. "
43
- f"Error: {e}"
49
+ "AMQP error while getting Dead Letter Exchange '%s'. " "Error: %s",
50
+ cls.DEAD_LETTER_EXCHANGE,
51
+ e,
44
52
  )
45
53
  raise
46
54
 
@@ -71,20 +79,24 @@ class RabbitmqUtils:
71
79
  )
72
80
  except ChannelNotFoundEntity as e:
73
81
  logger.error(
74
- f"Dead Letter Queue '{cls.DEAD_LETTER_QUEUE}' does not exist. "
75
- f"Please use the declare command to create it first. Error: {e}"
82
+ "Dead Letter Queue '%s' does not exist. "
83
+ "Please use the declare command to create it first. Error: %s",
84
+ cls.DEAD_LETTER_QUEUE,
85
+ e,
76
86
  )
77
87
  raise
78
88
  except ChannelClosed as e:
79
89
  logger.error(
80
- f"Channel closed while getting Dead Letter Queue '{cls.DEAD_LETTER_QUEUE}'. "
81
- f"Error: {e}"
90
+ "Channel closed while getting Dead Letter Queue '%s'. " "Error: %s",
91
+ cls.DEAD_LETTER_QUEUE,
92
+ e,
82
93
  )
83
94
  raise
84
95
  except AMQPError as e:
85
96
  logger.error(
86
- f"AMQP error while getting Dead Letter Queue '{cls.DEAD_LETTER_QUEUE}'. "
87
- f"Error: {e}"
97
+ "AMQP error while getting Dead Letter Queue '%s'. " "Error: %s",
98
+ cls.DEAD_LETTER_QUEUE,
99
+ e,
88
100
  )
89
101
  raise
90
102
 
@@ -120,19 +132,20 @@ class RabbitmqUtils:
120
132
  return dlx, dlq
121
133
  except ChannelNotFoundEntity as e:
122
134
  logger.error(
123
- f"Dead Letter infrastructure does not exist completely. "
124
- f"Please use the declare command to create it first. Error: {e}"
135
+ "Dead Letter infrastructure does not exist completely. "
136
+ "Please use the declare command to create it first. Error: %s",
137
+ e,
125
138
  )
126
139
  raise
127
140
  except ChannelClosed as e:
128
141
  logger.error(
129
- f"Channel closed while getting Dead Letter infrastructure. "
130
- f"Error: {e}"
142
+ "Channel closed while getting Dead Letter infrastructure. " "Error: %s",
143
+ e,
131
144
  )
132
145
  raise
133
146
  except AMQPError as e:
134
147
  logger.error(
135
- f"AMQP error while getting Dead Letter infrastructure. " f"Error: {e}"
148
+ "AMQP error while getting Dead Letter infrastructure. " "Error: %s", e
136
149
  )
137
150
  raise
138
151
 
@@ -161,19 +174,22 @@ class RabbitmqUtils:
161
174
  return await channel.get_exchange(exchange_name)
162
175
  except ChannelNotFoundEntity as e:
163
176
  logger.error(
164
- f"Exchange '{exchange_name}' does not exist. "
165
- f"Please use the declare command to create it first. Error: {e}"
177
+ "Exchange '%s' does not exist. "
178
+ "Please use the declare command to create it first. Error: %s",
179
+ exchange_name,
180
+ e,
166
181
  )
167
182
  raise
168
183
  except ChannelClosed as e:
169
184
  logger.error(
170
- f"Channel closed while getting exchange '{exchange_name}'. "
171
- f"Error: {e}"
185
+ "Channel closed while getting exchange '%s'. " "Error: %s",
186
+ exchange_name,
187
+ e,
172
188
  )
173
189
  raise
174
190
  except AMQPError as e:
175
191
  logger.error(
176
- f"AMQP error while getting exchange '{exchange_name}'. " f"Error: {e}"
192
+ "AMQP error while getting exchange '%s'. " "Error: %s", exchange_name, e
177
193
  )
178
194
  raise
179
195
 
@@ -206,18 +222,20 @@ class RabbitmqUtils:
206
222
  return await channel.get_queue(queue_name)
207
223
  except ChannelNotFoundEntity as e:
208
224
  logger.error(
209
- f"Queue '{queue_name}' does not exist. "
210
- f"Please use the declare command to create it first. Error: {e}"
225
+ "Queue '%s' does not exist. "
226
+ "Please use the declare command to create it first. Error: %s",
227
+ queue_name,
228
+ e,
211
229
  )
212
230
  raise
213
231
  except ChannelClosed as e:
214
232
  logger.error(
215
- f"Channel closed while getting queue '{queue_name}'. " f"Error: {e}"
233
+ "Channel closed while getting queue '%s'. " "Error: %s", queue_name, e
216
234
  )
217
235
  raise
218
236
  except AMQPError as e:
219
237
  logger.error(
220
- f"AMQP error while getting queue '{queue_name}'. " f"Error: {e}"
238
+ "AMQP error while getting queue '%s'. " "Error: %s", queue_name, e
221
239
  )
222
240
  raise
223
241
 
@@ -255,20 +273,24 @@ class RabbitmqUtils:
255
273
  return await channel.get_queue(queue_name)
256
274
  except ChannelNotFoundEntity as e:
257
275
  logger.error(
258
- f"Scheduler queue '{queue_name}' does not exist. "
259
- f"Please use the declare command to create it first. Error: {e}"
276
+ "Scheduler queue '%s' does not exist. "
277
+ "Please use the declare command to create it first. Error: %s",
278
+ queue_name,
279
+ e,
260
280
  )
261
281
  raise
262
282
  except ChannelClosed as e:
263
283
  logger.error(
264
- f"Channel closed while getting scheduler queue '{queue_name}'. "
265
- f"Error: {e}"
284
+ "Channel closed while getting scheduler queue '%s'. " "Error: %s",
285
+ queue_name,
286
+ e,
266
287
  )
267
288
  raise
268
289
  except AMQPError as e:
269
290
  logger.error(
270
- f"AMQP error while getting scheduler queue '{queue_name}'. "
271
- f"Error: {e}"
291
+ "AMQP error while getting scheduler queue '%s'. " "Error: %s",
292
+ queue_name,
293
+ e,
272
294
  )
273
295
  raise
274
296
 
@@ -310,15 +332,17 @@ class RabbitmqUtils:
310
332
  )
311
333
  except ChannelNotFoundEntity:
312
334
  # Exchange might not exist, which is fine
313
- logger.info(
314
- f"Exchange '{exchange_name}' does not exist, nothing to delete."
335
+ logger.debug(
336
+ "Exchange '%s' does not exist, nothing to delete.", exchange_name
315
337
  )
316
338
  except ChannelClosed as e:
317
339
  logger.warning(
318
- f"Channel closed while deleting exchange '{exchange_name}': {e}"
340
+ "Channel closed while deleting exchange '%s': %s", exchange_name, e
319
341
  )
320
342
  except AMQPError as e:
321
- logger.warning(f"AMQP error while deleting exchange '{exchange_name}': {e}")
343
+ logger.warning(
344
+ "AMQP error while deleting exchange '%s': %s", exchange_name, e
345
+ )
322
346
 
323
347
  @classmethod
324
348
  async def delete_queue(
@@ -339,8 +363,57 @@ class RabbitmqUtils:
339
363
  )
340
364
  except ChannelNotFoundEntity:
341
365
  # Queue might not exist, which is fine
342
- logger.info(f"Queue '{queue_name}' does not exist, nothing to delete.")
366
+ logger.debug("Queue '%s' does not exist, nothing to delete.", queue_name)
367
+ except ChannelClosed as e:
368
+ logger.warning(
369
+ "Channel closed while deleting queue '%s': %s", queue_name, e
370
+ )
371
+ except AMQPError as e:
372
+ logger.warning("AMQP error while deleting queue '%s': %s", queue_name, e)
373
+
374
+ @classmethod
375
+ async def get_dl_queue_message_count(cls, channel: AbstractChannel) -> int:
376
+ """
377
+ Get the message count in the Dead Letter Queue.
378
+ Returns 0 if the queue doesn't exist.
379
+ """
380
+ try:
381
+ await channel.get_queue(cls.DEAD_LETTER_QUEUE)
382
+ # The declaration property contains the message count
383
+ queue_info = await channel.declare_queue(
384
+ cls.DEAD_LETTER_QUEUE, passive=True
385
+ )
386
+ return queue_info.declaration_result.message_count or 0
387
+ except ChannelNotFoundEntity:
388
+ logger.debug("Dead Letter Queue does not exist.")
389
+ return 0
390
+ except ChannelClosed as e:
391
+ logger.error("Channel closed while getting DLQ message count: %s", e)
392
+ raise
393
+ except AMQPError as e:
394
+ logger.error("AMQP error while getting DLQ message count: %s", e)
395
+ raise
396
+
397
+ @classmethod
398
+ async def purge_dl_queue(cls, channel: AbstractChannel) -> int:
399
+ """
400
+ Purge all messages from the Dead Letter Queue.
401
+ Returns the number of messages purged.
402
+ """
403
+ try:
404
+ queue = await channel.get_queue(cls.DEAD_LETTER_QUEUE)
405
+ result = await queue.purge()
406
+ return result.message_count or 0
407
+ except ChannelNotFoundEntity as e:
408
+ logger.error(
409
+ "Dead Letter Queue '%s' does not exist. Error: %s",
410
+ cls.DEAD_LETTER_QUEUE,
411
+ e,
412
+ )
413
+ raise
343
414
  except ChannelClosed as e:
344
- logger.warning(f"Channel closed while deleting queue '{queue_name}': {e}")
415
+ logger.error("Channel closed while purging Dead Letter Queue: %s", e)
416
+ raise
345
417
  except AMQPError as e:
346
- logger.warning(f"AMQP error while deleting queue '{queue_name}': {e}")
418
+ logger.error("AMQP error while purging Dead Letter Queue: %s", e)
419
+ raise
jararaca/utils/retry.py CHANGED
@@ -1,3 +1,7 @@
1
+ # SPDX-FileCopyrightText: 2025 Lucas S
2
+ #
3
+ # SPDX-License-Identifier: GPL-3.0-or-later
4
+
1
5
  import asyncio
2
6
  import logging
3
7
  import random
@@ -10,7 +14,7 @@ P = ParamSpec("P")
10
14
  T = TypeVar("T")
11
15
 
12
16
 
13
- class RetryConfig:
17
+ class RetryPolicy:
14
18
  """Configuration for the retry mechanism."""
15
19
 
16
20
  def __init__(
@@ -45,7 +49,7 @@ async def retry_with_backoff(
45
49
  fn: Callable[[], Awaitable[T]],
46
50
  # args: P.args,
47
51
  # kwargs: P.kwargs,
48
- retry_config: Optional[RetryConfig] = None,
52
+ retry_policy: RetryPolicy,
49
53
  on_retry_callback: Optional[Callable[[int, E, float], None]] = None,
50
54
  retry_exceptions: tuple[type[E], ...] = (),
51
55
  ) -> T:
@@ -66,38 +70,39 @@ async def retry_with_backoff(
66
70
  Raises:
67
71
  The last exception encountered if all retries fail
68
72
  """
69
- if retry_config is None:
70
- retry_config = RetryConfig()
71
73
 
72
74
  last_exception = None
73
- delay = retry_config.initial_delay
75
+ delay = retry_policy.initial_delay
74
76
 
75
- for retry_count in range(retry_config.max_retries + 1):
77
+ for retry_count in range(retry_policy.max_retries + 1):
76
78
  try:
77
79
  return await fn()
78
80
  except retry_exceptions as e:
79
81
  last_exception = e
80
82
 
81
- if retry_count >= retry_config.max_retries:
83
+ if retry_count >= retry_policy.max_retries:
82
84
  logger.error(
83
- f"Max retries ({retry_config.max_retries}) exceeded: {str(e)}"
85
+ "Max retries (%s) exceeded: %s", retry_policy.max_retries, e
84
86
  )
85
87
  raise
86
88
 
87
89
  # Calculate next delay with exponential backoff
88
90
  if retry_count > 0: # Don't increase delay on the first failure
89
- delay = min(delay * retry_config.backoff_factor, retry_config.max_delay)
91
+ delay = min(delay * retry_policy.backoff_factor, retry_policy.max_delay)
90
92
 
91
93
  # Apply jitter if configured (±25% randomness)
92
- if retry_config.jitter:
94
+ if retry_policy.jitter:
93
95
  jitter_amount = delay * 0.25
94
96
  delay = delay + random.uniform(-jitter_amount, jitter_amount)
95
97
  # Ensure delay doesn't go negative due to jitter
96
98
  delay = max(delay, 0.1)
97
99
 
98
100
  logger.warning(
99
- f"Retry {retry_count+1}/{retry_config.max_retries} after error: {str(e)}. "
100
- f"Retrying in {delay:.2f}s"
101
+ "Retry %s/%s after error: %s. Retrying in %.2fs",
102
+ retry_count + 1,
103
+ retry_policy.max_retries,
104
+ e,
105
+ delay,
101
106
  )
102
107
 
103
108
  # Call the optional retry callback if provided
@@ -113,7 +118,7 @@ async def retry_with_backoff(
113
118
 
114
119
 
115
120
  def with_retry(
116
- retry_config: Optional[RetryConfig] = None,
121
+ retry_policy: RetryPolicy,
117
122
  retry_exceptions: tuple[type[Exception], ...] = (Exception,),
118
123
  ) -> Callable[[Callable[P, Awaitable[T]]], Callable[P, Awaitable[T]]]:
119
124
  """
@@ -132,7 +137,7 @@ def with_retry(
132
137
  async def wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
133
138
  return await retry_with_backoff(
134
139
  lambda: fn(*args, **kwargs),
135
- retry_config=retry_config,
140
+ retry_policy=retry_policy,
136
141
  retry_exceptions=retry_exceptions,
137
142
  )
138
143