qena-shared-lib 0.1.17__py3-none-any.whl → 0.1.19__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 (46) hide show
  1. qena_shared_lib/__init__.py +20 -2
  2. qena_shared_lib/alias.py +27 -0
  3. qena_shared_lib/application.py +4 -4
  4. qena_shared_lib/background.py +9 -7
  5. qena_shared_lib/cache.py +61 -0
  6. qena_shared_lib/enums.py +8 -0
  7. qena_shared_lib/eventbus.py +373 -0
  8. qena_shared_lib/exception_handling.py +409 -0
  9. qena_shared_lib/exceptions.py +167 -57
  10. qena_shared_lib/http/__init__.py +110 -0
  11. qena_shared_lib/{http.py → http/_base.py} +36 -36
  12. qena_shared_lib/http/_exception_handlers.py +202 -0
  13. qena_shared_lib/http/_request.py +24 -0
  14. qena_shared_lib/http/_response.py +24 -0
  15. qena_shared_lib/kafka/__init__.py +21 -0
  16. qena_shared_lib/kafka/_base.py +233 -0
  17. qena_shared_lib/kafka/_consumer.py +597 -0
  18. qena_shared_lib/kafka/_exception_handlers.py +124 -0
  19. qena_shared_lib/kafka/_producer.py +133 -0
  20. qena_shared_lib/logging.py +17 -13
  21. qena_shared_lib/mongodb.py +575 -0
  22. qena_shared_lib/rabbitmq/__init__.py +6 -6
  23. qena_shared_lib/rabbitmq/_base.py +68 -132
  24. qena_shared_lib/rabbitmq/_channel.py +2 -4
  25. qena_shared_lib/rabbitmq/_exception_handlers.py +69 -142
  26. qena_shared_lib/rabbitmq/_listener.py +245 -180
  27. qena_shared_lib/rabbitmq/_publisher.py +5 -5
  28. qena_shared_lib/rabbitmq/_rpc_client.py +21 -22
  29. qena_shared_lib/rabbitmq/message/__init__.py +19 -0
  30. qena_shared_lib/rabbitmq/message/_inbound.py +13 -0
  31. qena_shared_lib/rabbitmq/message/_outbound.py +13 -0
  32. qena_shared_lib/redis.py +47 -0
  33. qena_shared_lib/remotelogging/_base.py +34 -28
  34. qena_shared_lib/remotelogging/logstash/_base.py +3 -2
  35. qena_shared_lib/remotelogging/logstash/_http_sender.py +2 -4
  36. qena_shared_lib/remotelogging/logstash/_tcp_sender.py +2 -2
  37. qena_shared_lib/scheduler.py +24 -15
  38. qena_shared_lib/security.py +39 -32
  39. qena_shared_lib/sync.py +91 -0
  40. qena_shared_lib/utils.py +13 -11
  41. {qena_shared_lib-0.1.17.dist-info → qena_shared_lib-0.1.19.dist-info}/METADATA +395 -32
  42. qena_shared_lib-0.1.19.dist-info/RECORD +50 -0
  43. qena_shared_lib-0.1.19.dist-info/WHEEL +4 -0
  44. qena_shared_lib/exception_handlers.py +0 -235
  45. qena_shared_lib-0.1.17.dist-info/RECORD +0 -31
  46. qena_shared_lib-0.1.17.dist-info/WHEEL +0 -4
@@ -30,8 +30,9 @@ from pydantic import ValidationError
30
30
  from pydantic_core import from_json, to_json
31
31
 
32
32
  from ..dependencies.miscellaneous import validate_annotation
33
- from ..exceptions import RabbitMQServiceException
34
- from ..logging import LoggerProvider
33
+ from ..exception_handling import ServiceContext
34
+ from ..exceptions import RabbitMQBlockedError, RabbitMQServiceException
35
+ from ..logging import LoggerFactory
35
36
  from ..remotelogging import BaseRemoteLogSender
36
37
  from ..utils import AsyncEventLoopMixin, TypeAdapterCache
37
38
  from ._channel import BaseChannel
@@ -97,21 +98,29 @@ class RpcReply:
97
98
  self,
98
99
  channel_pool: ChannelPool,
99
100
  reply_to: str,
101
+ blocked_connection_check_callback: Callable[[], bool],
100
102
  correlation_id: str | None = None,
101
103
  ) -> None:
102
104
  self._channel_pool = channel_pool
103
105
  self._reply_to = reply_to
106
+ self._blocked_connection_check_callback = (
107
+ blocked_connection_check_callback
108
+ )
104
109
  self._correlation_id = correlation_id
105
110
  self._replied = False
106
111
 
107
112
  async def reply(self, message: Any) -> None:
108
113
  base_channel = await self._channel_pool.get()
109
-
110
114
  reply_properties = BasicProperties(content_type="application/json")
111
115
 
112
116
  if self._correlation_id is not None:
113
117
  reply_properties.correlation_id = self._correlation_id
114
118
 
119
+ if self._blocked_connection_check_callback():
120
+ raise RabbitMQBlockedError(
121
+ "rabbitmq broker is not able to accept message right now for manual reply"
122
+ )
123
+
115
124
  try:
116
125
  with base_channel as channel:
117
126
  channel.basic_publish(
@@ -131,7 +140,7 @@ class RpcReply:
131
140
 
132
141
 
133
142
  @dataclass
134
- class ListenerContext:
143
+ class ListenerContext(ServiceContext):
135
144
  queue: str
136
145
  listener_name: str
137
146
  body: bytes
@@ -218,6 +227,13 @@ class ListenerMethodContainer:
218
227
  dependencies: dict[str, type]
219
228
  retry_policy: RetryPolicy | None = None
220
229
 
230
+ def __post_init__(self) -> None:
231
+ self._is_async_listener = iscoroutinefunction(self.listener_method)
232
+
233
+ @property
234
+ def is_async_listener(self) -> bool:
235
+ return self._is_async_listener
236
+
221
237
 
222
238
  class ListenerChannelAdapter(BaseChannel):
223
239
  def __init__(
@@ -242,7 +258,7 @@ class ListenerChannelAdapter(BaseChannel):
242
258
 
243
259
 
244
260
  @dataclass
245
- class ListenerMessageMeta:
261
+ class ListenerMessageMetadata:
246
262
  body: bytes
247
263
  method: Basic.Deliver
248
264
  properties: BasicProperties
@@ -252,17 +268,17 @@ class ListenerMessageMeta:
252
268
 
253
269
 
254
270
  class Listener(AsyncEventLoopMixin):
255
- LISTENER_SUCCEEDED_COMSUMPTION = Counter(
271
+ _LISTENER_SUCCEEDED_COMSUMPTION = Counter(
256
272
  name="listener_succeeded_comsumption",
257
273
  documentation="Listener succeeded comsumption",
258
274
  labelnames=["queue", "listener_name"],
259
275
  )
260
- LISTENER_FAILED_COMSUMPTION = Counter(
276
+ _LISTENER_FAILED_COMSUMPTION = Counter(
261
277
  name="listener_failed_comsumption",
262
278
  documentation="Listener failed comsumption",
263
279
  labelnames=["queue", "listener_name", "exception"],
264
280
  )
265
- LISTENER_PROCESSING_LATENCY = Summary(
281
+ _LISTENER_PROCESSING_LATENCY = Summary(
266
282
  name="listener_processing_latency",
267
283
  documentation="Listener processing latency",
268
284
  labelnames=["queue", "listener_name"],
@@ -287,7 +303,7 @@ class Listener(AsyncEventLoopMixin):
287
303
  self._listeners_tasks_and_futures: list[Task[Any] | Future[Any]] = []
288
304
  self._consumer_tag: str | None = None
289
305
  self._cancelled = False
290
- self._logger = LoggerProvider.default().get_logger("rabbitmq.listener")
306
+ self._logger = LoggerFactory.get_logger("rabbitmq.listener")
291
307
 
292
308
  @property
293
309
  def queue(self) -> str:
@@ -356,6 +372,7 @@ class Listener(AsyncEventLoopMixin):
356
372
  connection: AsyncioConnection,
357
373
  channel_pool: ChannelPool,
358
374
  on_exception_callback: Callable[[ListenerContext, BaseException], bool],
375
+ blocked_connection_check_callback: Callable[[], bool],
359
376
  container: Container,
360
377
  remote_logger: BaseRemoteLogSender,
361
378
  global_retry_policy: RetryPolicy | None = None,
@@ -364,6 +381,9 @@ class Listener(AsyncEventLoopMixin):
364
381
  self._channel_pool = channel_pool
365
382
  self._listener_future = self.loop.create_future()
366
383
  self._on_exception_callback = on_exception_callback
384
+ self._blocked_connection_check_callback = (
385
+ blocked_connection_check_callback
386
+ )
367
387
  self._container = container
368
388
  self._remote_logger = remote_logger
369
389
  self._global_retry_policy = global_retry_policy
@@ -485,12 +505,13 @@ class Listener(AsyncEventLoopMixin):
485
505
  self._remote_logger.error(
486
506
  message=f"no listener registered with the name `{listener_name}` on queue `{self._queue}`",
487
507
  tags=[
488
- "RabbitMQ",
508
+ "rabbitmq",
509
+ "listener_doesnt_exist",
489
510
  self._queue,
490
511
  listener_name,
491
512
  ],
492
513
  extra={
493
- "serviceType": "RabbitMQ",
514
+ "serviceType": "rabbitmq",
494
515
  "queue": self._queue,
495
516
  "listenerName": listener_name,
496
517
  },
@@ -498,60 +519,38 @@ class Listener(AsyncEventLoopMixin):
498
519
 
499
520
  return
500
521
 
501
- listener_message_meta = ListenerMessageMeta(
502
- body=body,
503
- method=method,
504
- properties=properties,
505
- listener_name=listener_name,
506
- listener_method_container=listener_method_container,
507
- listener_start_time=time(),
508
- )
509
-
510
- self.loop.run_in_executor(
511
- executor=None,
512
- func=partial(self._parse_and_execute, listener_message_meta),
513
- ).add_done_callback(
514
- partial(self._on_submitted_listener_error, listener_message_meta)
515
- )
516
-
517
- def _on_submitted_listener_error(
518
- self, listener_message_meta: ListenerMessageMeta, future: Future[None]
519
- ) -> None:
520
- if future.cancelled():
521
- return
522
-
523
- exception = future.exception()
524
-
525
- if exception is not None:
526
- self._call_exception_callback(
527
- exception=exception,
528
- listener_message_meta=listener_message_meta,
529
- message=f"error occured while submitting listener callback on listener `{listener_message_meta.listener_name}` and queue `{self._queue}`",
522
+ self._parse_and_execute(
523
+ ListenerMessageMetadata(
524
+ body=body,
525
+ method=method,
526
+ properties=properties,
527
+ listener_name=listener_name,
528
+ listener_method_container=listener_method_container,
529
+ listener_start_time=time(),
530
530
  )
531
+ )
531
532
 
532
533
  def _parse_and_execute(
533
- self, listener_message_meta: ListenerMessageMeta
534
+ self, listener_message_metadata: ListenerMessageMetadata
534
535
  ) -> None:
535
536
  try:
536
537
  listener_method_args, listener_method_kwargs = self._parse_args(
537
- listener_message_meta
538
+ listener_message_metadata
538
539
  )
539
540
  except Exception as e:
540
541
  self._call_exception_callback(
541
542
  exception=e,
542
- listener_message_meta=listener_message_meta,
543
- message=f"arguments for listener `{listener_message_meta.listener_name}` in queue `{self._queue}` are not valid",
543
+ listener_message_metadata=listener_message_metadata,
544
+ message=f"arguments for listener `{listener_message_metadata.listener_name}` in queue `{self._queue}` are not valid",
544
545
  )
545
546
 
546
547
  return
547
548
 
548
549
  listener_task_or_future: Task[Any] | Future[Any] | None = None
549
550
 
550
- if iscoroutinefunction(
551
- listener_message_meta.listener_method_container.listener_method
552
- ):
551
+ if listener_message_metadata.listener_method_container.is_async_listener:
553
552
  listener_task_or_future = self.loop.create_task(
554
- listener_message_meta.listener_method_container.listener_method(
553
+ listener_message_metadata.listener_method_container.listener_method(
555
554
  *listener_method_args, **listener_method_kwargs
556
555
  )
557
556
  )
@@ -559,7 +558,7 @@ class Listener(AsyncEventLoopMixin):
559
558
  listener_task_or_future = self.loop.run_in_executor(
560
559
  executor=None,
561
560
  func=partial(
562
- listener_message_meta.listener_method_container.listener_method,
561
+ listener_message_metadata.listener_method_container.listener_method,
563
562
  *listener_method_args,
564
563
  **listener_method_kwargs,
565
564
  ),
@@ -569,16 +568,16 @@ class Listener(AsyncEventLoopMixin):
569
568
 
570
569
  self._listeners_tasks_and_futures.append(listener_task_or_future)
571
570
  listener_task_or_future.add_done_callback(
572
- partial(self._on_listener_done_executing, listener_message_meta)
571
+ partial(self._on_listener_done_executing, listener_message_metadata)
573
572
  )
574
573
 
575
574
  def _parse_args(
576
- self, listener_message_meta: ListenerMessageMeta
575
+ self, listener_message_metadata: ListenerMessageMetadata
577
576
  ) -> tuple[list[Any], dict[str, Any]]:
578
577
  try:
579
- message = from_json(listener_message_meta.body)
578
+ message = from_json(listener_message_metadata.body)
580
579
  except:
581
- message = listener_message_meta.body.decode()
580
+ message = listener_message_metadata.body.decode()
582
581
 
583
582
  assigned_args: list[Any] = []
584
583
  listener_method_args = []
@@ -593,8 +592,8 @@ class Listener(AsyncEventLoopMixin):
593
592
  for (
594
593
  parameter_name,
595
594
  parameter,
596
- ) in listener_message_meta.listener_method_container.parameters.items():
597
- dependency_key = listener_message_meta.listener_method_container.dependencies.get(
595
+ ) in listener_message_metadata.listener_method_container.parameters.items():
596
+ dependency_key = listener_message_metadata.listener_method_container.dependencies.get(
598
597
  parameter_name
599
598
  )
600
599
  dependency = None
@@ -609,8 +608,8 @@ class Listener(AsyncEventLoopMixin):
609
608
  ):
610
609
  listener_context = ListenerContext(
611
610
  queue=self._queue,
612
- listener_name=listener_message_meta.listener_name,
613
- body=listener_message_meta.body,
611
+ listener_name=listener_message_metadata.listener_name,
612
+ body=listener_message_metadata.body,
614
613
  flow_control=self._flow_control,
615
614
  )
616
615
 
@@ -709,8 +708,8 @@ class Listener(AsyncEventLoopMixin):
709
708
  for (
710
709
  parameter_name,
711
710
  parameter,
712
- ) in listener_message_meta.listener_method_container.parameters.items():
713
- dependency = listener_message_meta.listener_method_container.dependencies.get(
711
+ ) in listener_message_metadata.listener_method_container.parameters.items():
712
+ dependency = listener_message_metadata.listener_method_container.dependencies.get(
714
713
  parameter_name
715
714
  )
716
715
 
@@ -721,8 +720,8 @@ class Listener(AsyncEventLoopMixin):
721
720
  listener_method_args.append(
722
721
  ListenerContext(
723
722
  queue=self._queue,
724
- listener_name=listener_message_meta.listener_name,
725
- body=listener_message_meta.body,
723
+ listener_name=listener_message_metadata.listener_name,
724
+ body=listener_message_metadata.body,
726
725
  flow_control=self._flow_control,
727
726
  )
728
727
  )
@@ -773,7 +772,7 @@ class Listener(AsyncEventLoopMixin):
773
772
 
774
773
  def _on_listener_done_executing(
775
774
  self,
776
- listener_message_meta: ListenerMessageMeta,
775
+ listener_message_metadata: ListenerMessageMetadata,
777
776
  task_or_future: Task[Any] | Future[Any],
778
777
  ) -> None:
779
778
  if (
@@ -785,14 +784,14 @@ class Listener(AsyncEventLoopMixin):
785
784
  if task_or_future.cancelled():
786
785
  return
787
786
 
788
- self._observe_listener_time(listener_message_meta)
787
+ self._observe_listener_time(listener_message_metadata)
789
788
 
790
789
  exception = task_or_future.exception()
791
790
 
792
791
  if exception is not None:
793
- if listener_message_meta.properties.reply_to is None:
792
+ if listener_message_metadata.properties.reply_to is None:
794
793
  retry_policy = (
795
- listener_message_meta.listener_method_container.retry_policy
794
+ listener_message_metadata.listener_method_container.retry_policy
796
795
  or self._retry_policy
797
796
  or self._global_retry_policy
798
797
  )
@@ -812,12 +811,10 @@ class Listener(AsyncEventLoopMixin):
812
811
  ):
813
812
  times_rejected = None
814
813
 
815
- if listener_message_meta.properties.headers is not None:
814
+ if listener_message_metadata.properties.headers is not None:
816
815
  try:
817
- _times_rejected = (
818
- listener_message_meta.properties.headers.get(
819
- "times_rejected"
820
- )
816
+ _times_rejected = listener_message_metadata.properties.headers.get(
817
+ "times_rejected"
821
818
  )
822
819
 
823
820
  if isinstance(_times_rejected, int):
@@ -835,7 +832,7 @@ class Listener(AsyncEventLoopMixin):
835
832
 
836
833
  if retry_policy.can_retry(times_rejected):
837
834
  self._reject_message(
838
- listener_message_meta=listener_message_meta,
835
+ listener_message_metadata=listener_message_metadata,
839
836
  retry_policy=retry_policy,
840
837
  times_rejected=times_rejected,
841
838
  )
@@ -844,43 +841,44 @@ class Listener(AsyncEventLoopMixin):
844
841
 
845
842
  self._call_exception_callback(
846
843
  exception=exception,
847
- listener_message_meta=listener_message_meta,
848
- message=f"error occured while executing listener `{listener_message_meta.listener_name}` in queue `{self._queue}`",
844
+ listener_message_metadata=listener_message_metadata,
845
+ message=f"error occured while executing listener `{listener_message_metadata.listener_name}` in queue `{self._queue}`",
849
846
  )
850
847
 
851
848
  if (
852
- listener_message_meta.properties.reply_to is not None
849
+ listener_message_metadata.properties.reply_to is not None
853
850
  and exception is None
854
851
  ):
855
852
  self._reply_response(
856
- listener_message_meta=listener_message_meta,
853
+ listener_message_metadata=listener_message_metadata,
857
854
  response=task_or_future.result(),
858
855
  )
859
856
 
860
857
  self._logger.debug(
861
858
  "message from queue `%s` consumed by listener `%s`",
862
859
  self.queue,
863
- listener_message_meta.listener_name,
860
+ listener_message_metadata.listener_name,
864
861
  )
865
862
 
866
863
  if exception is not None:
867
- self.LISTENER_FAILED_COMSUMPTION.labels(
864
+ self._LISTENER_FAILED_COMSUMPTION.labels(
868
865
  queue=self._queue,
869
- listener_name=listener_message_meta.listener_name,
866
+ listener_name=listener_message_metadata.listener_name,
870
867
  exception=exception.__class__.__name__,
871
868
  ).inc()
872
869
  else:
873
- self.LISTENER_SUCCEEDED_COMSUMPTION.labels(
870
+ self._LISTENER_SUCCEEDED_COMSUMPTION.labels(
874
871
  queue=self._queue,
875
- listener_name=listener_message_meta.listener_name,
872
+ listener_name=listener_message_metadata.listener_name,
876
873
  ).inc()
877
874
 
878
875
  def _observe_listener_time(
879
- self, listener_message_meta: ListenerMessageMeta
876
+ self, listener_message_metadata: ListenerMessageMetadata
880
877
  ) -> None:
881
- self.LISTENER_PROCESSING_LATENCY.labels(
882
- queue=self._queue, listener_name=listener_message_meta.listener_name
883
- ).observe(listener_message_meta.listener_start_time - time())
878
+ self._LISTENER_PROCESSING_LATENCY.labels(
879
+ queue=self._queue,
880
+ listener_name=listener_message_metadata.listener_name,
881
+ ).observe(listener_message_metadata.listener_start_time - time())
884
882
 
885
883
  def _is_recoverable_exception(
886
884
  self,
@@ -905,7 +903,7 @@ class Listener(AsyncEventLoopMixin):
905
903
  ):
906
904
  return True
907
905
 
908
- cause = cause.__cause__ or exception.__context__
906
+ cause = cause.__cause__ or cause.__context__
909
907
 
910
908
  return False
911
909
 
@@ -922,84 +920,86 @@ class Listener(AsyncEventLoopMixin):
922
920
  def _call_exception_callback(
923
921
  self,
924
922
  exception: BaseException,
925
- listener_message_meta: ListenerMessageMeta,
923
+ listener_message_metadata: ListenerMessageMetadata,
926
924
  message: str | None = None,
927
925
  ) -> None:
928
926
  context_dispose_callback = None
929
927
  rpc_reply = None
928
+ tags = [
929
+ "rabbitmq",
930
+ self._queue,
931
+ listener_message_metadata.listener_name,
932
+ ]
933
+ extra = {
934
+ "serviceType": "rabbitmq",
935
+ "queue": self._queue,
936
+ "listenerName": listener_message_metadata.listener_name,
937
+ "section": "exceptionHandlerCallback",
938
+ "raisedException": exception.__class__.__name__,
939
+ }
930
940
 
931
- if listener_message_meta.properties.reply_to is not None:
941
+ if listener_message_metadata.properties.reply_to is not None:
932
942
 
933
943
  def on_context_disposed(context: ListenerContext) -> None:
934
- assert listener_message_meta.properties.reply_to is not None
944
+ assert listener_message_metadata.properties.reply_to is not None
935
945
  assert context.rpc_reply is not None
936
946
 
937
947
  if not context.rpc_reply.replied:
938
948
  self._reply_response(
939
- listener_message_meta=listener_message_meta,
949
+ listener_message_metadata=listener_message_metadata,
940
950
  response=self._reponse_from_exception(exception),
941
951
  )
942
952
 
943
953
  context_dispose_callback = on_context_disposed
944
954
  rpc_reply = RpcReply(
945
955
  channel_pool=self._channel_pool,
946
- reply_to=listener_message_meta.properties.reply_to,
947
- correlation_id=listener_message_meta.properties.correlation_id,
956
+ reply_to=listener_message_metadata.properties.reply_to,
957
+ blocked_connection_check_callback=self._blocked_connection_check_callback,
958
+ correlation_id=listener_message_metadata.properties.correlation_id,
948
959
  )
949
960
 
950
961
  try:
951
962
  exception_callback_succeeded = self._on_exception_callback(
952
963
  ListenerContext(
953
964
  queue=self._queue,
954
- listener_name=listener_message_meta.listener_name,
955
- body=listener_message_meta.body,
965
+ listener_name=listener_message_metadata.listener_name,
966
+ body=listener_message_metadata.body,
956
967
  flow_control=self._flow_control,
957
968
  rpc_reply=rpc_reply,
958
969
  context_dispose_callback=context_dispose_callback,
970
+ ).set_labels(
971
+ {
972
+ "queue": self._queue,
973
+ "listener_name": listener_message_metadata.listener_name,
974
+ "exception": exception.__class__.__name__,
975
+ }
959
976
  ),
960
977
  exception,
961
978
  )
962
979
  except:
980
+ tags.append("exception_callback_error")
963
981
  self._remote_logger.exception(
964
- message=f"error occured while invoking rabbitmq exception handler callback in listener `{listener_message_meta.listener_name}` and queue `{self._queue}`",
965
- tags=[
966
- "RabbitMQ",
967
- self._queue,
968
- listener_message_meta.listener_name,
969
- ],
970
- extra={
971
- "serviceType": "RabbitMQ",
972
- "queue": self._queue,
973
- "listenerName": listener_message_meta.listener_name,
974
- "section": "exceptionHandlerCallback",
975
- "raisedException": exception.__class__.__name__,
976
- },
982
+ message=f"error occured while invoking rabbitmq exception handler callback in listener `{listener_message_metadata.listener_name}` and queue `{self._queue}`",
983
+ tags=tags,
984
+ extra=extra,
977
985
  )
978
986
 
979
987
  return
980
988
 
981
989
  if not exception_callback_succeeded:
990
+ tags.append("exception_callback_unsuccessful")
982
991
  self._remote_logger.exception(
983
992
  message=(
984
993
  message
985
- or f"error occured while handling event in listener `{listener_message_meta.listener_name}` and queue `{self._queue}`"
994
+ or f"error occured while handling event in listener `{listener_message_metadata.listener_name}` and queue `{self._queue}`"
986
995
  ),
987
- tags=[
988
- "RabbitMQ",
989
- self._queue,
990
- listener_message_meta.listener_name,
991
- ],
992
- extra={
993
- "serviceType": "RabbitMQ",
994
- "queue": self._queue,
995
- "listenerName": listener_message_meta.listener_name,
996
- "raisedException": exception.__class__.__name__,
997
- },
996
+ tags=tags,
997
+ extra=extra,
998
998
  )
999
999
 
1000
1000
  def _reject_message(
1001
1001
  self,
1002
- listener_message_meta: ListenerMessageMeta,
1002
+ listener_message_metadata: ListenerMessageMetadata,
1003
1003
  retry_policy: RetryPolicy,
1004
1004
  times_rejected: int,
1005
1005
  ) -> None:
@@ -1007,7 +1007,7 @@ class Listener(AsyncEventLoopMixin):
1007
1007
 
1008
1008
  self._logger.debug(
1009
1009
  "message will be redelivered to listenr `%s` on queue `%s` after `%f` seconds, times redelivered `%d`",
1010
- listener_message_meta.listener_name,
1010
+ listener_message_metadata.listener_name,
1011
1011
  self._queue,
1012
1012
  message_redelivery_delay,
1013
1013
  times_rejected,
@@ -1016,27 +1016,27 @@ class Listener(AsyncEventLoopMixin):
1016
1016
  delay=message_redelivery_delay,
1017
1017
  callback=partial(
1018
1018
  self._on_time_to_redeliver_message,
1019
- listener_message_meta,
1019
+ listener_message_metadata,
1020
1020
  times_rejected,
1021
1021
  ),
1022
1022
  )
1023
1023
 
1024
1024
  def _on_time_to_redeliver_message(
1025
1025
  self,
1026
- listener_message_meta: ListenerMessageMeta,
1026
+ listener_message_metadata: ListenerMessageMetadata,
1027
1027
  times_rejected: int,
1028
1028
  ) -> None:
1029
1029
  self.loop.create_task(self._channel_pool.get()).add_done_callback(
1030
1030
  partial(
1031
1031
  self._on_redelivery_channel_found,
1032
- listener_message_meta,
1032
+ listener_message_metadata,
1033
1033
  times_rejected,
1034
1034
  )
1035
1035
  )
1036
1036
 
1037
1037
  def _on_redelivery_channel_found(
1038
1038
  self,
1039
- listener_message_meta: ListenerMessageMeta,
1039
+ listener_message_metadata: ListenerMessageMetadata,
1040
1040
  times_rejected: int,
1041
1041
  task: Task[BaseChannel],
1042
1042
  ) -> None:
@@ -1048,66 +1048,84 @@ class Listener(AsyncEventLoopMixin):
1048
1048
  if exception is not None:
1049
1049
  self._call_exception_callback(
1050
1050
  exception=exception,
1051
- listener_message_meta=listener_message_meta,
1052
- message=f"error occured while getting channel from pool in listener `{listener_message_meta.listener_name}` and queue `{self._queue}` after rejecteed {times_rejected} times",
1051
+ listener_message_metadata=listener_message_metadata,
1052
+ message=f"error occured while getting channel from pool in listener `{listener_message_metadata.listener_name}` and queue `{self._queue}` after rejecteed {times_rejected} times",
1053
1053
  )
1054
1054
 
1055
1055
  return
1056
1056
 
1057
1057
  headers = {
1058
- self._listener_name_header_key: listener_message_meta.listener_name,
1058
+ self._listener_name_header_key: listener_message_metadata.listener_name,
1059
1059
  "times_rejected": times_rejected + 1,
1060
1060
  }
1061
1061
 
1062
- if listener_message_meta.properties.headers is None:
1063
- listener_message_meta.properties.headers = headers
1062
+ if listener_message_metadata.properties.headers is None:
1063
+ listener_message_metadata.properties.headers = headers
1064
1064
  else:
1065
1065
  cast(
1066
- dict[str, Any], listener_message_meta.properties.headers
1066
+ dict[str, Any], listener_message_metadata.properties.headers
1067
1067
  ).update(headers)
1068
1068
 
1069
+ if self._blocked_connection_check_callback():
1070
+ self._remote_logger.error(
1071
+ message=f"couldn't redeliver message to queue `{self._queue}` and listener `{listener_message_metadata.listener_name}` due to blocked connection",
1072
+ tags=[
1073
+ "rabbitmq",
1074
+ "redelivery_connection_blocked",
1075
+ self._queue,
1076
+ listener_message_metadata.listener_name,
1077
+ ],
1078
+ extra={
1079
+ "serviceType": "rabbitmq",
1080
+ "queue": self._queue,
1081
+ "listenerName": listener_message_metadata.listener_name,
1082
+ },
1083
+ )
1084
+
1085
+ return
1086
+
1069
1087
  try:
1070
1088
  with task.result() as channel:
1071
1089
  channel.basic_publish(
1072
1090
  exchange=DEFAULT_EXCHANGE,
1073
1091
  routing_key=self._queue,
1074
- body=listener_message_meta.body,
1075
- properties=listener_message_meta.properties,
1092
+ body=listener_message_metadata.body,
1093
+ properties=listener_message_metadata.properties,
1076
1094
  )
1077
1095
  except Exception as e:
1078
1096
  self._call_exception_callback(
1079
1097
  exception=e,
1080
- listener_message_meta=listener_message_meta,
1081
- message=f"error occured while sending event for redelivery in listener `{listener_message_meta.listener_name}` and queue `{self._queue}` after rejecteed {times_rejected} times",
1098
+ listener_message_metadata=listener_message_metadata,
1099
+ message=f"error occured while sending event for redelivery in listener `{listener_message_metadata.listener_name}` and queue `{self._queue}` after rejecteed {times_rejected} times",
1082
1100
  )
1083
1101
 
1084
1102
  return
1085
1103
 
1086
1104
  self._logger.debug(
1087
1105
  "message queued for redelivery to `%s` on queue `%s`, times redelivered `%d`",
1088
- listener_message_meta.listener_name,
1106
+ listener_message_metadata.listener_name,
1089
1107
  self._queue,
1090
1108
  times_rejected + 1,
1091
1109
  )
1092
1110
 
1093
1111
  def _reply_response(
1094
1112
  self,
1095
- listener_message_meta: ListenerMessageMeta,
1113
+ listener_message_metadata: ListenerMessageMetadata,
1096
1114
  response: Any,
1097
1115
  ) -> None:
1098
- assert listener_message_meta.properties.reply_to is not None
1116
+ assert listener_message_metadata.properties.reply_to is not None
1099
1117
 
1100
1118
  reponse_properties = BasicProperties(content_type="application/json")
1101
1119
 
1102
- if listener_message_meta.properties.correlation_id is None:
1120
+ if listener_message_metadata.properties.correlation_id is None:
1103
1121
  self._logger.warning(
1104
1122
  "`correlation_id` property not supplied for listener `%s` and queue `%s`",
1105
- listener_message_meta.listener_name,
1123
+ listener_message_metadata.listener_name,
1106
1124
  self._queue,
1107
1125
  )
1108
1126
  else:
1109
1127
  reponse_properties.correlation_id = (
1110
- listener_message_meta.properties.correlation_id
1128
+ listener_message_metadata.properties.correlation_id
1111
1129
  )
1112
1130
 
1113
1131
  try:
@@ -1115,8 +1133,8 @@ class Listener(AsyncEventLoopMixin):
1115
1133
  except Exception as e:
1116
1134
  self._call_exception_callback(
1117
1135
  exception=e,
1118
- listener_message_meta=listener_message_meta,
1119
- message=f"listener response is not json serializable in listener `{listener_message_meta.listener_name}` and queue `{self._queue}`",
1136
+ listener_message_metadata=listener_message_metadata,
1137
+ message=f"listener response is not json serializable in listener `{listener_message_metadata.listener_name}` and queue `{self._queue}`",
1120
1138
  )
1121
1139
 
1122
1140
  return
@@ -1124,7 +1142,7 @@ class Listener(AsyncEventLoopMixin):
1124
1142
  self.loop.create_task(self._channel_pool.get()).add_done_callback(
1125
1143
  partial(
1126
1144
  self._on_reply_channel_found,
1127
- listener_message_meta,
1145
+ listener_message_metadata,
1128
1146
  response_body,
1129
1147
  reponse_properties,
1130
1148
  )
@@ -1156,7 +1174,7 @@ class Listener(AsyncEventLoopMixin):
1156
1174
 
1157
1175
  def _on_reply_channel_found(
1158
1176
  self,
1159
- listener_message_meta: ListenerMessageMeta,
1177
+ listener_message_metadata: ListenerMessageMetadata,
1160
1178
  response_body: bytes,
1161
1179
  response_properties: BasicProperties,
1162
1180
  task: Task[BaseChannel],
@@ -1169,36 +1187,54 @@ class Listener(AsyncEventLoopMixin):
1169
1187
  if exception is not None:
1170
1188
  self._call_exception_callback(
1171
1189
  exception=exception,
1172
- listener_message_meta=listener_message_meta,
1173
- message=f"error occured while getting channel for publishing response in listener `{listener_message_meta.listener_name}` and queue `{self._queue}`",
1190
+ listener_message_metadata=listener_message_metadata,
1191
+ message=f"error occured while getting channel for publishing response in listener `{listener_message_metadata.listener_name}` and queue `{self._queue}`",
1174
1192
  )
1175
1193
 
1176
1194
  return
1177
1195
 
1178
- try:
1179
- assert listener_message_meta.properties.reply_to is not None
1196
+ if self._blocked_connection_check_callback():
1197
+ self._remote_logger.error(
1198
+ message=f"couldn't repond to rpc call on queue `{self._queue}` and listener `{listener_message_metadata.listener_name}` due to blocked connection",
1199
+ tags=[
1200
+ "rabbitmq",
1201
+ "reply_connection_blocked",
1202
+ self._queue,
1203
+ listener_message_metadata.listener_name,
1204
+ ],
1205
+ extra={
1206
+ "serviceType": "rabbitmq",
1207
+ "queue": self._queue,
1208
+ "listenerName": listener_message_metadata.listener_name,
1209
+ },
1210
+ )
1211
+
1212
+ return
1180
1213
 
1214
+ assert listener_message_metadata.properties.reply_to is not None
1215
+
1216
+ try:
1181
1217
  with task.result() as channel:
1182
1218
  channel.basic_publish(
1183
1219
  exchange=DEFAULT_EXCHANGE,
1184
- routing_key=listener_message_meta.properties.reply_to,
1220
+ routing_key=listener_message_metadata.properties.reply_to,
1185
1221
  properties=response_properties,
1186
1222
  body=response_body,
1187
1223
  )
1188
1224
  except Exception as e:
1189
1225
  self._call_exception_callback(
1190
1226
  exception=e,
1191
- listener_message_meta=listener_message_meta,
1192
- message=f"error occured while publishing response to rpc call in listener `{listener_message_meta.listener_name}` and queue `{self._queue}`",
1227
+ listener_message_metadata=listener_message_metadata,
1228
+ message=f"error occured while publishing response to rpc call in listener `{listener_message_metadata.listener_name}` and queue `{self._queue}`",
1193
1229
  )
1194
1230
 
1195
1231
  return
1196
1232
 
1197
1233
  self._logger.debug(
1198
1234
  "sent a reply to `%s` for request from queue `%s` and listener `%s`",
1199
- listener_message_meta.properties.reply_to,
1235
+ listener_message_metadata.properties.reply_to,
1200
1236
  self._queue,
1201
- listener_message_meta.listener_name,
1237
+ listener_message_metadata.listener_name,
1202
1238
  )
1203
1239
 
1204
1240
  def __call__(self, listener: type[L]) -> type[L]:
@@ -1207,6 +1243,13 @@ class Listener(AsyncEventLoopMixin):
1207
1243
  return listener
1208
1244
 
1209
1245
 
1246
+ @dataclass
1247
+ class ListenerMethodMetadata:
1248
+ listener_type: type[Listener]
1249
+ listener_name: str | None = None
1250
+ retry_policy: RetryPolicy | None = None
1251
+
1252
+
1210
1253
  class Consumer(Listener):
1211
1254
  def __init__(
1212
1255
  self,
@@ -1266,7 +1309,11 @@ def consume(
1266
1309
  setattr(
1267
1310
  consumer_method,
1268
1311
  CONSUMER_ATTRIBUTE,
1269
- ListenerMethodMeta(listener_name=target, retry_policy=retry_policy),
1312
+ ListenerMethodMetadata(
1313
+ listener_type=Consumer,
1314
+ listener_name=target,
1315
+ retry_policy=retry_policy,
1316
+ ),
1270
1317
  )
1271
1318
 
1272
1319
  return consumer_method
@@ -1317,7 +1364,11 @@ def execute(
1317
1364
  )
1318
1365
 
1319
1366
  setattr(
1320
- worker_method, RPC_WORKER_ATTRIBUTE, ListenerMethodMeta(procedure)
1367
+ worker_method,
1368
+ RPC_WORKER_ATTRIBUTE,
1369
+ ListenerMethodMetadata(
1370
+ listener_type=RpcWorker, listener_name=procedure
1371
+ ),
1321
1372
  )
1322
1373
 
1323
1374
  return worker_method
@@ -1325,13 +1376,9 @@ def execute(
1325
1376
  return wrapper
1326
1377
 
1327
1378
 
1328
- @dataclass
1329
- class ListenerMethodMeta:
1330
- listener_name: str | None = None
1331
- retry_policy: RetryPolicy | None = None
1332
-
1333
-
1334
1379
  class ListenerBase:
1380
+ _LISTENER_ATTRIBUTES = {CONSUMER_ATTRIBUTE, RPC_WORKER_ATTRIBUTE}
1381
+
1335
1382
  def get_inner_listener(self) -> Listener:
1336
1383
  listener = getattr(self, LISTENER_ATTRIBUTE, None)
1337
1384
 
@@ -1352,32 +1399,35 @@ class ListenerBase:
1352
1399
  if attribute is None:
1353
1400
  continue
1354
1401
 
1355
- listener_method_attribute, listener_method, listener_method_meta = (
1356
- self._validate_listener_method_attribute(
1357
- attribute=attribute,
1358
- listener_method_attribute=CONSUMER_ATTRIBUTE,
1359
- previous_listener_method_attribute=listener_method_attribute,
1360
- )
1361
- )
1402
+ listener_method_attribute = None
1403
+ listener_method = None
1404
+ listener_method_metadata = None
1405
+
1406
+ for listener_attribute in self._LISTENER_ATTRIBUTES:
1407
+ if (
1408
+ listener_method is not None
1409
+ and listener_method_metadata is not None
1410
+ ):
1411
+ break
1362
1412
 
1363
- if listener_method is None or listener_method_meta is None:
1364
1413
  (
1365
1414
  listener_method_attribute,
1366
1415
  listener_method,
1367
- listener_method_meta,
1416
+ listener_method_metadata,
1368
1417
  ) = self._validate_listener_method_attribute(
1369
1418
  attribute=attribute,
1370
- listener_method_attribute=RPC_WORKER_ATTRIBUTE,
1419
+ listener_method_attribute=listener_attribute,
1371
1420
  previous_listener_method_attribute=listener_method_attribute,
1421
+ listener=listener,
1372
1422
  )
1373
1423
 
1374
- if listener_method is None or listener_method_meta is None:
1424
+ if listener_method is None or listener_method_metadata is None:
1375
1425
  continue
1376
1426
 
1377
1427
  listener.add_listener_method(
1378
- listener_name=listener_method_meta.listener_name,
1428
+ listener_name=listener_method_metadata.listener_name,
1379
1429
  listener_method=listener_method,
1380
- retry_policy=listener_method_meta.retry_policy,
1430
+ retry_policy=listener_method_metadata.retry_policy,
1381
1431
  )
1382
1432
 
1383
1433
  return listener
@@ -1387,19 +1437,21 @@ class ListenerBase:
1387
1437
  attribute: Any,
1388
1438
  listener_method_attribute: str,
1389
1439
  previous_listener_method_attribute: str | None,
1440
+ listener: Listener,
1390
1441
  ) -> tuple[
1391
- str | None, Callable[..., Any] | None, ListenerMethodMeta | None
1442
+ str | None, Callable[..., Any] | None, ListenerMethodMetadata | None
1392
1443
  ]:
1393
- listener_method_meta = getattr(
1444
+ listener_method_metadata = getattr(
1394
1445
  attribute, listener_method_attribute, None
1395
1446
  )
1396
1447
 
1397
- if listener_method_meta is None:
1448
+ if listener_method_metadata is None:
1398
1449
  return previous_listener_method_attribute or None, None, None
1399
1450
 
1400
- if not isinstance(listener_method_meta, ListenerMethodMeta):
1451
+ if not isinstance(listener_method_metadata, ListenerMethodMetadata):
1401
1452
  raise TypeError(
1402
- f"expected `{listener_method_attribute}` to by of type `ListenerMethodMeta`, got {type(listener_method_meta)}"
1453
+ f"expected `{listener_method_attribute}` to by of type `ListenerMethodMetadata`, "
1454
+ f"got {type(listener_method_metadata)}"
1403
1455
  )
1404
1456
 
1405
1457
  if (
@@ -1413,4 +1465,17 @@ class ListenerBase:
1413
1465
  f"object annotated with `{listener_method_attribute}` is not callable"
1414
1466
  )
1415
1467
 
1416
- return listener_method_attribute, attribute, listener_method_meta
1468
+ if (
1469
+ listener_method_metadata.listener_type
1470
+ not in listener.__class__.mro()
1471
+ ):
1472
+ listener_mro = ", ".join(
1473
+ map(lambda c: c.__name__, listener.__class__.mro())
1474
+ )
1475
+
1476
+ raise TypeError(
1477
+ f"listener method is not correct member of `{listener_method_metadata.listener_type.__name__}`, "
1478
+ f"got `[{listener_mro}]` super classes"
1479
+ )
1480
+
1481
+ return listener_method_attribute, attribute, listener_method_metadata