jararaca 0.4.0a5__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 (35) hide show
  1. jararaca/__init__.py +9 -9
  2. jararaca/cli.py +643 -4
  3. jararaca/core/providers.py +4 -0
  4. jararaca/helpers/__init__.py +3 -0
  5. jararaca/helpers/global_scheduler/__init__.py +3 -0
  6. jararaca/helpers/global_scheduler/config.py +21 -0
  7. jararaca/helpers/global_scheduler/controller.py +42 -0
  8. jararaca/helpers/global_scheduler/registry.py +32 -0
  9. jararaca/messagebus/decorators.py +104 -10
  10. jararaca/messagebus/interceptors/aiopika_publisher_interceptor.py +50 -8
  11. jararaca/messagebus/interceptors/message_publisher_collector.py +62 -0
  12. jararaca/messagebus/interceptors/publisher_interceptor.py +25 -3
  13. jararaca/messagebus/worker.py +276 -200
  14. jararaca/microservice.py +3 -1
  15. jararaca/observability/providers/otel.py +31 -13
  16. jararaca/persistence/base.py +1 -1
  17. jararaca/persistence/utilities.py +47 -24
  18. jararaca/presentation/decorators.py +3 -3
  19. jararaca/reflect/decorators.py +24 -10
  20. jararaca/reflect/helpers.py +18 -0
  21. jararaca/rpc/http/__init__.py +2 -2
  22. jararaca/rpc/http/decorators.py +9 -9
  23. jararaca/scheduler/beat_worker.py +14 -14
  24. jararaca/tools/typescript/decorators.py +4 -4
  25. jararaca/tools/typescript/interface_parser.py +3 -1
  26. jararaca/utils/env_parse_utils.py +133 -0
  27. jararaca/utils/rabbitmq_utils.py +47 -0
  28. jararaca/utils/retry.py +11 -13
  29. {jararaca-0.4.0a5.dist-info → jararaca-0.4.0a19.dist-info}/METADATA +2 -1
  30. {jararaca-0.4.0a5.dist-info → jararaca-0.4.0a19.dist-info}/RECORD +35 -27
  31. pyproject.toml +2 -1
  32. {jararaca-0.4.0a5.dist-info → jararaca-0.4.0a19.dist-info}/LICENSE +0 -0
  33. {jararaca-0.4.0a5.dist-info → jararaca-0.4.0a19.dist-info}/LICENSES/GPL-3.0-or-later.txt +0 -0
  34. {jararaca-0.4.0a5.dist-info → jararaca-0.4.0a19.dist-info}/WHEEL +0 -0
  35. {jararaca-0.4.0a5.dist-info → jararaca-0.4.0a19.dist-info}/entry_points.txt +0 -0
jararaca/microservice.py CHANGED
@@ -59,6 +59,8 @@ class MessageBusTransactionData:
59
59
  topic: str
60
60
  message: MessageOf[Message]
61
61
  message_type: Type[Message]
62
+ message_id: str | None = field(default=None)
63
+ processing_attempt: int = field(default=0)
62
64
  context_type: Literal["message_bus"] = "message_bus"
63
65
 
64
66
 
@@ -309,7 +311,7 @@ class Container:
309
311
  self.register(instance, bind_to)
310
312
  return cast(T, instance)
311
313
 
312
- def register(self, instance: T, bind_to: Any) -> None:
314
+ def register(self, instance: Any, bind_to: Any) -> None:
313
315
  self.instances_map[bind_to] = instance
314
316
 
315
317
  def get_by_type(self, token: Type[T]) -> T:
@@ -4,7 +4,7 @@
4
4
 
5
5
  import logging
6
6
  from contextlib import asynccontextmanager, contextmanager
7
- from typing import Any, AsyncGenerator, Generator, Literal, Protocol
7
+ from typing import TYPE_CHECKING, Any, AsyncGenerator, Generator, Literal, Protocol
8
8
 
9
9
  from opentelemetry import metrics, trace
10
10
  from opentelemetry._logs import set_logger_provider
@@ -49,6 +49,10 @@ from jararaca.observability.decorators import (
49
49
  )
50
50
  from jararaca.observability.interceptor import ObservabilityProvider
51
51
 
52
+ if TYPE_CHECKING:
53
+ from opentelemetry.trace import Span as _Span
54
+ from typing_extensions import TypeIs
55
+
52
56
  tracer: trace.Tracer = trace.get_tracer(__name__)
53
57
 
54
58
 
@@ -82,11 +86,13 @@ def extract_context_attributes(ctx: AppTransactionContext) -> dict[str, Any]:
82
86
  }
83
87
  elif tx_data.context_type == "message_bus":
84
88
  extra_attributes = {
89
+ "bus.message.id": tx_data.message_id,
85
90
  "bus.message.name": tx_data.message_type.__qualname__,
86
91
  "bus.message.module": tx_data.message_type.__module__,
87
92
  "bus.message.category": tx_data.message_type.MESSAGE_CATEGORY,
88
93
  "bus.message.type": tx_data.message_type.MESSAGE_TYPE,
89
94
  "bus.message.topic": tx_data.message_type.MESSAGE_TOPIC,
95
+ "bus.message.processing_attempt": tx_data.processing_attempt,
90
96
  }
91
97
  elif tx_data.context_type == "websocket":
92
98
  extra_attributes = {
@@ -190,13 +196,13 @@ class OtelTracingContextProviderFactory(TracingContextProviderFactory):
190
196
 
191
197
  @asynccontextmanager
192
198
  async def root_setup(
193
- self, app_tx_ctx: AppTransactionContext
199
+ self, app_context: AppTransactionContext
194
200
  ) -> AsyncGenerator[None, None]:
195
201
 
196
202
  title: str = "Unmapped App Context Execution"
197
203
  headers: dict[str, Any] = {}
198
- tx_data = app_tx_ctx.transaction_data
199
- extra_attributes = extract_context_attributes(app_tx_ctx)
204
+ tx_data = app_context.transaction_data
205
+ extra_attributes = extract_context_attributes(app_context)
200
206
 
201
207
  if tx_data.context_type == "http":
202
208
  headers = dict(tx_data.request.headers)
@@ -206,7 +212,7 @@ class OtelTracingContextProviderFactory(TracingContextProviderFactory):
206
212
  ].decode(errors="ignore")
207
213
 
208
214
  elif tx_data.context_type == "message_bus":
209
- title = f"Message Bus {tx_data.topic}"
215
+ title = f"Att#{tx_data.processing_attempt} Message Bus {tx_data.topic}"
210
216
  headers = use_implicit_headers() or {}
211
217
 
212
218
  elif tx_data.context_type == "websocket":
@@ -242,12 +248,12 @@ class OtelTracingContextProviderFactory(TracingContextProviderFactory):
242
248
  ) as root_span:
243
249
  cx = root_span.get_span_context()
244
250
  span_traceparent_id = hex(cx.trace_id)[2:].rjust(32, "0")
245
- if app_tx_ctx.transaction_data.context_type == "http":
246
- app_tx_ctx.transaction_data.request.scope[TRACEPARENT_KEY] = (
251
+ if app_context.transaction_data.context_type == "http":
252
+ app_context.transaction_data.request.scope[TRACEPARENT_KEY] = (
247
253
  span_traceparent_id
248
254
  )
249
- elif app_tx_ctx.transaction_data.context_type == "websocket":
250
- app_tx_ctx.transaction_data.websocket.scope[TRACEPARENT_KEY] = (
255
+ elif app_context.transaction_data.context_type == "websocket":
256
+ app_context.transaction_data.websocket.scope[TRACEPARENT_KEY] = (
251
257
  span_traceparent_id
252
258
  )
253
259
  tracing_headers: ImplicitHeaders = {}
@@ -262,6 +268,16 @@ class LoggerHandlerCallback(Protocol):
262
268
  def __call__(self, logger_handler: logging.Handler) -> None: ...
263
269
 
264
270
 
271
+ class SpanWithName(Protocol):
272
+
273
+ @property
274
+ def name(self) -> str: ...
275
+
276
+
277
+ def is_span_with_name(span: Any) -> "TypeIs[SpanWithName]":
278
+ return hasattr(span, "name")
279
+
280
+
265
281
  class CustomLoggingHandler(LoggingHandler):
266
282
 
267
283
  def _translate(self, record: logging.LogRecord) -> dict[str, Any]:
@@ -270,14 +286,16 @@ class CustomLoggingHandler(LoggingHandler):
270
286
  data = super()._translate(record)
271
287
  extra_attributes = extract_context_attributes(ctx)
272
288
 
273
- current_span = trace.get_current_span()
289
+ current_span: "_Span" = trace.get_current_span()
274
290
 
275
291
  data["attributes"] = {
276
292
  **data.get("attributes", {}),
277
293
  **extra_attributes,
278
294
  **(
279
295
  {
280
- "span_name": current_span.name,
296
+ "span_name": (
297
+ current_span.name if is_span_with_name(current_span) else ""
298
+ ),
281
299
  }
282
300
  if hasattr(current_span, "name")
283
301
  and current_span.is_recording() is False
@@ -298,7 +316,7 @@ class OtelObservabilityProvider(ObservabilityProvider):
298
316
  logs_exporter: LogExporter,
299
317
  span_exporter: SpanExporter,
300
318
  meter_exporter: MeterExporter,
301
- logging_handler_callback: LoggerHandlerCallback = lambda _: None,
319
+ logging_handler_callback: LoggerHandlerCallback = lambda logger_handler: None,
302
320
  meter_export_interval: int = 5000,
303
321
  ) -> None:
304
322
  self.app_name = app_name
@@ -356,7 +374,7 @@ class OtelObservabilityProvider(ObservabilityProvider):
356
374
  def from_url(
357
375
  app_name: str,
358
376
  url: str,
359
- logging_handler_callback: LoggerHandlerCallback = lambda _: None,
377
+ logging_handler_callback: LoggerHandlerCallback = lambda logger_handler: None,
360
378
  meter_export_interval: int = 5000,
361
379
  ) -> "OtelObservabilityProvider":
362
380
  """
@@ -43,7 +43,7 @@ BASED_BASE_ENTITY_T = TypeVar("BASED_BASE_ENTITY_T", bound="BaseEntity")
43
43
  class BaseEntity(AsyncAttrs, DeclarativeBase):
44
44
 
45
45
  @classmethod
46
- def from_basemodel(cls, mutation: T_BASEMODEL) -> "Self":
46
+ def from_basemodel(cls, mutation: BaseModel) -> "Self":
47
47
  intersection = set(cls.__annotations__.keys()) & set(
48
48
  mutation.__class__.model_fields.keys()
49
49
  )
@@ -321,6 +321,11 @@ class QueryOperations(Generic[QUERY_FILTER_T, QUERY_ENTITY_T]):
321
321
  | Select[Tuple[QUERY_ENTITY_T]]
322
322
  | None
323
323
  ) = None,
324
+ final_listing_statement: (
325
+ Callable[[Select[Tuple[QUERY_ENTITY_T]]], Select[Tuple[QUERY_ENTITY_T]]]
326
+ | None
327
+ ) = None,
328
+ total_type: Literal["total_over", "count_subquery"] = "total_over",
324
329
  ) -> "Paginated[QUERY_ENTITY_T]":
325
330
  """
326
331
  Executes a query with the provided filter and interceptors.
@@ -333,9 +338,9 @@ class QueryOperations(Generic[QUERY_FILTER_T, QUERY_ENTITY_T]):
333
338
 
334
339
  initial_statement = self.base_statement or select(self.entity_type)
335
340
 
336
- if base_statement and callable(base_statement):
341
+ if base_statement is not None and callable(base_statement):
337
342
  initial_statement = base_statement(initial_statement)
338
- elif base_statement and isinstance(base_statement, Select):
343
+ elif base_statement is not None and isinstance(base_statement, Select):
339
344
  initial_statement = base_statement
340
345
 
341
346
  tier_one_filtered_query = self.generate_filtered_query(
@@ -362,32 +367,50 @@ class QueryOperations(Generic[QUERY_FILTER_T, QUERY_ENTITY_T]):
362
367
  )
363
368
  )
364
369
 
365
- paginated_query = tier_two_filtered_query.add_columns(
366
- func.count().over().label("total_count")
367
- )
368
- paginated_query = paginated_query.limit(filter.page_size).offset(
369
- (filter.page) * filter.page_size
370
- )
370
+ if final_listing_statement is not None:
371
+ tier_two_filtered_query = final_listing_statement(tier_two_filtered_query)
371
372
 
372
- result = await self.session.execute(paginated_query)
373
- result = self.judge_unique(result)
374
- rows = result.all()
373
+ if total_type == "total_over":
374
+ # Use window function for total count (single query)
375
+ paginated_query = tier_two_filtered_query.add_columns(
376
+ func.count().over().label("total_count")
377
+ )
378
+ paginated_query = paginated_query.limit(filter.page_size).offset(
379
+ (filter.page) * filter.page_size
380
+ )
375
381
 
376
- if rows:
377
- unpaginated_total = rows[0].total_count
378
- result_scalars = [row[0] for row in rows]
379
- else:
380
- result_scalars = []
381
- if filter.page == 0:
382
- unpaginated_total = 0
382
+ result = await self.session.execute(paginated_query)
383
+ result = self.judge_unique(result)
384
+ rows = result.all()
385
+
386
+ if rows:
387
+ unpaginated_total = rows[0].total_count
388
+ result_scalars = [row[0] for row in rows]
383
389
  else:
384
- unpaginated_total = (
385
- await self.session.execute(
386
- select(func.count()).select_from(
387
- tier_two_filtered_query.subquery()
388
- )
390
+ result_scalars = []
391
+ unpaginated_total = 0
392
+ else: # total_type == "count_subquery"
393
+ # Use separate count query (two queries)
394
+ paginated_query = tier_two_filtered_query.limit(filter.page_size).offset(
395
+ (filter.page) * filter.page_size
396
+ )
397
+
398
+ result = await self.session.execute(paginated_query)
399
+ result = self.judge_unique(result)
400
+ result_scalars = list(result.scalars())
401
+
402
+ # Always fetch total with separate query
403
+ unpaginated_total = (
404
+ await self.session.execute(
405
+ tier_two_filtered_query.with_only_columns(
406
+ func.count(self.entity_type.id)
407
+ ).order_by(None)
408
+ if issubclass(self.entity_type, IdentifiableEntity)
409
+ else select(func.count()).select_from(
410
+ tier_two_filtered_query.subquery()
389
411
  )
390
- ).scalar_one()
412
+ )
413
+ ).scalar_one()
391
414
 
392
415
  return Paginated(
393
416
  items=result_scalars,
@@ -20,7 +20,7 @@ from jararaca.reflect.controller_inspect import (
20
20
  ControllerMemberReflect,
21
21
  inspect_controller,
22
22
  )
23
- from jararaca.reflect.decorators import DECORATED_T, StackableDecorator
23
+ from jararaca.reflect.decorators import FUNC_OR_TYPE_T, StackableDecorator
24
24
 
25
25
  DECORATED_TYPE = TypeVar("DECORATED_TYPE", bound=Any)
26
26
  DECORATED_FUNC = TypeVar("DECORATED_FUNC", bound=Callable[..., Any])
@@ -61,11 +61,11 @@ class RestController(StackableDecorator):
61
61
  raise Exception("Router factory is not set")
62
62
  return self.router_factory
63
63
 
64
- def post_decorated(self, subject: DECORATED_T) -> None:
64
+ def post_decorated(self, subject: FUNC_OR_TYPE_T) -> None:
65
65
 
66
66
  def router_factory(
67
67
  lifecycle: AppLifecycle,
68
- instance: DECORATED_T,
68
+ instance: FUNC_OR_TYPE_T,
69
69
  ) -> APIRouter:
70
70
  assert inspect.isclass(
71
71
  subject
@@ -2,23 +2,25 @@
2
2
  #
3
3
  # SPDX-License-Identifier: GPL-3.0-or-later
4
4
 
5
- from typing import Any, Callable, Self, TypedDict, TypeVar, cast
5
+ from typing import Any, Callable, Generic, Self, TypedDict, TypeVar, cast
6
6
 
7
- DECORATED_T = TypeVar("DECORATED_T", bound="Callable[..., Any] | type")
7
+ FUNC_OR_TYPE_T = Callable[..., Any] | type
8
8
 
9
+ DECORATED_T = TypeVar("DECORATED_T", bound="FUNC_OR_TYPE_T")
9
10
 
10
- S = TypeVar("S", bound="StackableDecorator")
11
+
12
+ S = TypeVar("S", bound="BaseStackableDecorator")
11
13
 
12
14
 
13
15
  class DecoratorMetadata(TypedDict):
14
- decorators: "list[StackableDecorator]"
15
- decorators_by_type: "dict[Any, list[StackableDecorator]]"
16
+ decorators: "list[BaseStackableDecorator]"
17
+ decorators_by_type: "dict[Any, list[BaseStackableDecorator]]"
16
18
 
17
19
 
18
- class StackableDecorator:
20
+ class BaseStackableDecorator:
19
21
  _ATTR_NAME: str = "__jararaca_stackable_decorator__"
20
22
 
21
- def __call__(self, subject: DECORATED_T) -> DECORATED_T:
23
+ def __call__(self, subject: Any) -> Any:
22
24
  self.pre_decorated(subject)
23
25
  self.register(subject, self)
24
26
  self.post_decorated(subject)
@@ -45,7 +47,7 @@ class StackableDecorator:
45
47
  return None
46
48
 
47
49
  @classmethod
48
- def register(cls, subject: Any, decorator: "StackableDecorator") -> None:
50
+ def register(cls, subject: Any, decorator: "BaseStackableDecorator") -> None:
49
51
  if not cls._ATTR_NAME:
50
52
  raise NotImplementedError("Subclasses must define _ATTR_NAME")
51
53
 
@@ -95,13 +97,13 @@ class StackableDecorator:
95
97
  return decorators[-1]
96
98
  return None
97
99
 
98
- def pre_decorated(self, subject: DECORATED_T) -> None:
100
+ def pre_decorated(self, subject: FUNC_OR_TYPE_T) -> None:
99
101
  """
100
102
  Hook method called before the subject is decorated.
101
103
  Can be overridden by subclasses to perform additional setup.
102
104
  """
103
105
 
104
- def post_decorated(self, subject: DECORATED_T) -> None:
106
+ def post_decorated(self, subject: FUNC_OR_TYPE_T) -> None:
105
107
  """
106
108
  Hook method called after the subject has been decorated.
107
109
  Can be overridden by subclasses to perform additional setup.
@@ -148,6 +150,18 @@ class StackableDecorator:
148
150
  )
149
151
 
150
152
 
153
+ class StackableDecorator(BaseStackableDecorator):
154
+
155
+ def __call__(self, subject: DECORATED_T) -> DECORATED_T:
156
+ return cast(DECORATED_T, super().__call__(subject))
157
+
158
+
159
+ class GenericStackableDecorator(BaseStackableDecorator, Generic[DECORATED_T]):
160
+
161
+ def __call__(self, subject: DECORATED_T) -> DECORATED_T:
162
+ return cast(DECORATED_T, super().__call__(subject))
163
+
164
+
151
165
  def resolve_class_decorators(
152
166
  subject: Any, decorator_cls: type[S], inherit: bool = True
153
167
  ) -> list[S]:
@@ -0,0 +1,18 @@
1
+ # SPDX-FileCopyrightText: 2025 Lucas S
2
+ #
3
+ # SPDX-License-Identifier: GPL-3.0-or-later
4
+
5
+
6
+ from typing import TYPE_CHECKING, get_args, get_origin
7
+
8
+ if TYPE_CHECKING:
9
+ from types import GenericAlias
10
+
11
+ from typing_extensions import TypeIs
12
+
13
+
14
+ def is_generic_alias(subject: object) -> "TypeIs[GenericAlias]":
15
+ origin = get_origin(subject)
16
+ args = get_args(subject)
17
+
18
+ return origin is not None and len(args) > 0
@@ -43,9 +43,9 @@ from .decorators import ( # HTTP Method decorators; Request parameter decorator
43
43
  ResponseMiddleware,
44
44
  RestClient,
45
45
  Retry,
46
- RetryConfig,
47
46
  RouteHttpErrorHandler,
48
47
  RPCRequestNetworkError,
48
+ RPCRetryPolicy,
49
49
  RPCUnhandleError,
50
50
  Timeout,
51
51
  TimeoutException,
@@ -85,7 +85,7 @@ __all__ = [
85
85
  "RequestHook",
86
86
  "ResponseHook",
87
87
  # Configuration classes
88
- "RetryConfig",
88
+ "RPCRetryPolicy",
89
89
  # Data structures
90
90
  "HttpRPCRequest",
91
91
  "HttpRPCResponse",
@@ -27,7 +27,8 @@ from jararaca.reflect.decorators import StackableDecorator
27
27
 
28
28
  logger = logging.getLogger(__name__)
29
29
 
30
- DECORATED_FUNC = TypeVar("DECORATED_FUNC", bound=Callable[..., Awaitable[Any]])
30
+ FUNC_T = Callable[..., Awaitable[Any]]
31
+ DECORATED_FUNC = TypeVar("DECORATED_FUNC", bound=FUNC_T)
31
32
  DECORATED_CLASS = TypeVar("DECORATED_CLASS", bound=Any)
32
33
 
33
34
 
@@ -143,11 +144,11 @@ class Timeout:
143
144
  return func
144
145
 
145
146
  @staticmethod
146
- def get(func: DECORATED_FUNC) -> Optional["Timeout"]:
147
+ def get(func: FUNC_T) -> Optional["Timeout"]:
147
148
  return getattr(func, Timeout.TIMEOUT_ATTR, None)
148
149
 
149
150
 
150
- class RetryConfig:
151
+ class RPCRetryPolicy:
151
152
  """Configuration for retry behavior"""
152
153
 
153
154
  def __init__(
@@ -166,7 +167,7 @@ class Retry:
166
167
 
167
168
  RETRY_ATTR = "__request_retry__"
168
169
 
169
- def __init__(self, config: RetryConfig):
170
+ def __init__(self, config: RPCRetryPolicy):
170
171
  self.config = config
171
172
 
172
173
  def __call__(self, func: DECORATED_FUNC) -> DECORATED_FUNC:
@@ -174,7 +175,7 @@ class Retry:
174
175
  return func
175
176
 
176
177
  @staticmethod
177
- def get(func: DECORATED_FUNC) -> Optional["Retry"]:
178
+ def get(func: FUNC_T) -> Optional["Retry"]:
178
179
  return getattr(func, Retry.RETRY_ATTR, None)
179
180
 
180
181
 
@@ -191,7 +192,7 @@ class ContentType:
191
192
  return func
192
193
 
193
194
  @staticmethod
194
- def get(func: DECORATED_FUNC) -> Optional["ContentType"]:
195
+ def get(func: FUNC_T) -> Optional["ContentType"]:
195
196
  return getattr(func, ContentType.CONTENT_TYPE_ATTR, None)
196
197
 
197
198
 
@@ -390,7 +391,7 @@ class HttpRpcClientBuilder:
390
391
  self._response_hooks = response_hooks
391
392
 
392
393
  async def _execute_with_retry(
393
- self, request: HttpRPCRequest, retry_config: Optional[RetryConfig]
394
+ self, request: HttpRPCRequest, retry_config: Optional[RPCRetryPolicy]
394
395
  ) -> HttpRPCResponse:
395
396
  """Execute request with retry logic"""
396
397
  if not retry_config:
@@ -660,7 +661,7 @@ __all__ = [
660
661
  "FormData",
661
662
  "File",
662
663
  "Timeout",
663
- "RetryConfig",
664
+ "RPCRetryPolicy",
664
665
  "Retry",
665
666
  "ContentType",
666
667
  "RestClient",
@@ -679,7 +680,6 @@ __all__ = [
679
680
  "BasicAuth",
680
681
  "ApiKeyAuth",
681
682
  "CacheMiddleware",
682
- "TracedRequestMiddleware",
683
683
  "GlobalHttpErrorHandler",
684
684
  "RouteHttpErrorHandler",
685
685
  "HandleHttpErrorCallback",
@@ -43,7 +43,7 @@ from jararaca.scheduler.decorators import (
43
43
  )
44
44
  from jararaca.scheduler.types import DelayedMessageData
45
45
  from jararaca.utils.rabbitmq_utils import RabbitmqUtils
46
- from jararaca.utils.retry import RetryConfig, retry_with_backoff
46
+ from jararaca.utils.retry import RetryPolicy, retry_with_backoff
47
47
 
48
48
  logger = logging.getLogger(__name__)
49
49
 
@@ -168,7 +168,7 @@ class _RabbitMQBrokerDispatcher(_MessageBrokerDispatcher):
168
168
 
169
169
  return await retry_with_backoff(
170
170
  _establish_connection,
171
- retry_config=self.config.connection_retry_config,
171
+ retry_policy=self.config.connection_retry_config,
172
172
  retry_exceptions=(
173
173
  AMQPConnectionError,
174
174
  ConnectionError,
@@ -189,7 +189,7 @@ class _RabbitMQBrokerDispatcher(_MessageBrokerDispatcher):
189
189
 
190
190
  return await retry_with_backoff(
191
191
  _establish_channel,
192
- retry_config=self.config.connection_retry_config,
192
+ retry_policy=self.config.connection_retry_config,
193
193
  retry_exceptions=(
194
194
  AMQPConnectionError,
195
195
  AMQPChannelError,
@@ -219,7 +219,7 @@ class _RabbitMQBrokerDispatcher(_MessageBrokerDispatcher):
219
219
  try:
220
220
  await retry_with_backoff(
221
221
  _dispatch,
222
- retry_config=self.config.dispatch_retry_config,
222
+ retry_policy=self.config.dispatch_retry_config,
223
223
  retry_exceptions=(
224
224
  AMQPConnectionError,
225
225
  AMQPChannelError,
@@ -261,13 +261,13 @@ class _RabbitMQBrokerDispatcher(_MessageBrokerDispatcher):
261
261
  aio_pika.Message(
262
262
  body=delayed_message.payload,
263
263
  ),
264
- routing_key=f"{delayed_message.message_topic}.",
264
+ routing_key=f"{delayed_message.message_topic}.#",
265
265
  )
266
266
 
267
267
  try:
268
268
  await retry_with_backoff(
269
269
  _dispatch,
270
- retry_config=self.config.dispatch_retry_config,
270
+ retry_policy=self.config.dispatch_retry_config,
271
271
  retry_exceptions=(
272
272
  AMQPConnectionError,
273
273
  AMQPChannelError,
@@ -306,7 +306,7 @@ class _RabbitMQBrokerDispatcher(_MessageBrokerDispatcher):
306
306
  logger.debug("Initializing RabbitMQ connection...")
307
307
  await retry_with_backoff(
308
308
  _initialize,
309
- retry_config=self.config.connection_retry_config,
309
+ retry_policy=self.config.connection_retry_config,
310
310
  retry_exceptions=(
311
311
  AMQPConnectionError,
312
312
  AMQPChannelError,
@@ -447,8 +447,8 @@ def _get_message_broker_dispatcher_from_url(
447
447
  class BeatWorkerConfig:
448
448
  """Configuration for beat worker connection resilience"""
449
449
 
450
- connection_retry_config: RetryConfig = field(
451
- default_factory=lambda: RetryConfig(
450
+ connection_retry_config: RetryPolicy = field(
451
+ default_factory=lambda: RetryPolicy(
452
452
  max_retries=10,
453
453
  initial_delay=2.0,
454
454
  max_delay=60.0,
@@ -456,8 +456,8 @@ class BeatWorkerConfig:
456
456
  jitter=True,
457
457
  )
458
458
  )
459
- dispatch_retry_config: RetryConfig = field(
460
- default_factory=lambda: RetryConfig(
459
+ dispatch_retry_config: RetryPolicy = field(
460
+ default_factory=lambda: RetryPolicy(
461
461
  max_retries=3,
462
462
  initial_delay=1.0,
463
463
  max_delay=10.0,
@@ -561,7 +561,7 @@ class BeatWorker:
561
561
 
562
562
  # Ensure we have a healthy connection before starting the main loop
563
563
  if (
564
- hasattr(self.broker, "connection_healthy")
564
+ isinstance(self.broker, _RabbitMQBrokerDispatcher)
565
565
  and not self.broker.connection_healthy
566
566
  ):
567
567
  logger.error("Connection not healthy at start of processing loop. Exiting.")
@@ -570,7 +570,7 @@ class BeatWorker:
570
570
  while not self.shutdown_event.is_set():
571
571
  # Check connection health before processing scheduled actions
572
572
  if (
573
- hasattr(self.broker, "connection_healthy")
573
+ isinstance(self.broker, _RabbitMQBrokerDispatcher)
574
574
  and not self.broker.connection_healthy
575
575
  ):
576
576
  logger.error("Broker connection is not healthy. Exiting.")
@@ -729,7 +729,7 @@ class BeatWorker:
729
729
 
730
730
  # Check if broker connection is healthy
731
731
  if (
732
- hasattr(self.broker, "connection_healthy")
732
+ isinstance(self.broker, _RabbitMQBrokerDispatcher)
733
733
  and self.broker.connection_healthy
734
734
  ):
735
735
  logger.debug("Broker connection is healthy")
@@ -67,11 +67,11 @@ class SplitInputOutput(StackableDecorator):
67
67
  pass
68
68
 
69
69
  @staticmethod
70
- def is_split_model(cls: type) -> bool:
70
+ def is_split_model(cls_type: type) -> bool:
71
71
  """
72
72
  Check if the Pydantic model is marked for split interface generation.
73
73
  """
74
- return SplitInputOutput.get_last(cls) is not None
74
+ return SplitInputOutput.get_last(cls_type) is not None
75
75
 
76
76
 
77
77
  class ExposeType:
@@ -106,11 +106,11 @@ class ExposeType:
106
106
  return cls
107
107
 
108
108
  @staticmethod
109
- def is_exposed_type(cls: type) -> bool:
109
+ def is_exposed_type(cls_type: type) -> bool:
110
110
  """
111
111
  Check if the type is marked for explicit exposure.
112
112
  """
113
- return getattr(cls, ExposeType.METADATA_KEY, False)
113
+ return getattr(cls_type, ExposeType.METADATA_KEY, False)
114
114
 
115
115
  @staticmethod
116
116
  def get_all_exposed_types() -> set[type]:
@@ -23,6 +23,7 @@ from typing import (
23
23
  Literal,
24
24
  Type,
25
25
  TypeVar,
26
+ cast,
26
27
  get_origin,
27
28
  )
28
29
  from uuid import UUID
@@ -554,7 +555,8 @@ def parse_single_typescript_interface(
554
555
  mapped_types.update(inherited_classes)
555
556
 
556
557
  if Enum in inherited_classes:
557
- enum_values = sorted([(x._name_, x.value) for x in basemodel_type])
558
+ enum_casted = cast(Type[Enum], basemodel_type)
559
+ enum_values = sorted([(x._name_, x.value) for x in enum_casted])
558
560
  return (
559
561
  set(),
560
562
  f"export enum {basemodel_type.__name__}{interface_suffix} {{\n"