qena-shared-lib 0.1.16__py3-none-any.whl → 0.1.18__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 (33) hide show
  1. qena_shared_lib/__init__.py +3 -2
  2. qena_shared_lib/application.py +4 -4
  3. qena_shared_lib/background.py +9 -7
  4. qena_shared_lib/exception_handling.py +409 -0
  5. qena_shared_lib/exceptions.py +170 -57
  6. qena_shared_lib/http/__init__.py +90 -0
  7. qena_shared_lib/{http.py → http/_base.py} +36 -36
  8. qena_shared_lib/http/_exception_handlers.py +202 -0
  9. qena_shared_lib/kafka/__init__.py +21 -0
  10. qena_shared_lib/kafka/_base.py +233 -0
  11. qena_shared_lib/kafka/_consumer.py +597 -0
  12. qena_shared_lib/kafka/_exception_handlers.py +124 -0
  13. qena_shared_lib/kafka/_producer.py +133 -0
  14. qena_shared_lib/logging.py +17 -13
  15. qena_shared_lib/rabbitmq/__init__.py +4 -6
  16. qena_shared_lib/rabbitmq/_base.py +68 -132
  17. qena_shared_lib/rabbitmq/_channel.py +2 -4
  18. qena_shared_lib/rabbitmq/_exception_handlers.py +69 -142
  19. qena_shared_lib/rabbitmq/_listener.py +246 -157
  20. qena_shared_lib/rabbitmq/_publisher.py +5 -5
  21. qena_shared_lib/rabbitmq/_rpc_client.py +21 -22
  22. qena_shared_lib/remotelogging/_base.py +20 -20
  23. qena_shared_lib/remotelogging/logstash/_base.py +2 -2
  24. qena_shared_lib/remotelogging/logstash/_http_sender.py +2 -4
  25. qena_shared_lib/remotelogging/logstash/_tcp_sender.py +2 -2
  26. qena_shared_lib/scheduler.py +24 -15
  27. qena_shared_lib/security.py +39 -32
  28. qena_shared_lib/utils.py +13 -11
  29. {qena_shared_lib-0.1.16.dist-info → qena_shared_lib-0.1.18.dist-info}/METADATA +9 -1
  30. qena_shared_lib-0.1.18.dist-info/RECORD +38 -0
  31. qena_shared_lib/exception_handlers.py +0 -235
  32. qena_shared_lib-0.1.16.dist-info/RECORD +0 -31
  33. {qena_shared_lib-0.1.16.dist-info → qena_shared_lib-0.1.18.dist-info}/WHEEL +0 -0
@@ -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,7 +519,7 @@ class Listener(AsyncEventLoopMixin):
498
519
 
499
520
  return
500
521
 
501
- listener_message_meta = ListenerMessageMeta(
522
+ listener_message_metadata = ListenerMessageMetadata(
502
523
  body=body,
503
524
  method=method,
504
525
  properties=properties,
@@ -509,13 +530,17 @@ class Listener(AsyncEventLoopMixin):
509
530
 
510
531
  self.loop.run_in_executor(
511
532
  executor=None,
512
- func=partial(self._parse_and_execute, listener_message_meta),
533
+ func=partial(self._parse_and_execute, listener_message_metadata),
513
534
  ).add_done_callback(
514
- partial(self._on_submitted_listener_error, listener_message_meta)
535
+ partial(
536
+ self._on_submitted_listener_error, listener_message_metadata
537
+ )
515
538
  )
516
539
 
517
540
  def _on_submitted_listener_error(
518
- self, listener_message_meta: ListenerMessageMeta, future: Future[None]
541
+ self,
542
+ listener_message_metadata: ListenerMessageMetadata,
543
+ future: Future[None],
519
544
  ) -> None:
520
545
  if future.cancelled():
521
546
  return
@@ -525,33 +550,31 @@ class Listener(AsyncEventLoopMixin):
525
550
  if exception is not None:
526
551
  self._call_exception_callback(
527
552
  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}`",
553
+ listener_message_metadata=listener_message_metadata,
554
+ message=f"error occured while submitting listener callback on listener `{listener_message_metadata.listener_name}` and queue `{self._queue}`",
530
555
  )
531
556
 
532
557
  def _parse_and_execute(
533
- self, listener_message_meta: ListenerMessageMeta
558
+ self, listener_message_metadata: ListenerMessageMetadata
534
559
  ) -> None:
535
560
  try:
536
561
  listener_method_args, listener_method_kwargs = self._parse_args(
537
- listener_message_meta
562
+ listener_message_metadata
538
563
  )
539
564
  except Exception as e:
540
565
  self._call_exception_callback(
541
566
  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",
567
+ listener_message_metadata=listener_message_metadata,
568
+ message=f"arguments for listener `{listener_message_metadata.listener_name}` in queue `{self._queue}` are not valid",
544
569
  )
545
570
 
546
571
  return
547
572
 
548
573
  listener_task_or_future: Task[Any] | Future[Any] | None = None
549
574
 
550
- if iscoroutinefunction(
551
- listener_message_meta.listener_method_container.listener_method
552
- ):
575
+ if listener_message_metadata.listener_method_container.is_async_listener:
553
576
  listener_task_or_future = self.loop.create_task(
554
- listener_message_meta.listener_method_container.listener_method(
577
+ listener_message_metadata.listener_method_container.listener_method(
555
578
  *listener_method_args, **listener_method_kwargs
556
579
  )
557
580
  )
@@ -559,7 +582,7 @@ class Listener(AsyncEventLoopMixin):
559
582
  listener_task_or_future = self.loop.run_in_executor(
560
583
  executor=None,
561
584
  func=partial(
562
- listener_message_meta.listener_method_container.listener_method,
585
+ listener_message_metadata.listener_method_container.listener_method,
563
586
  *listener_method_args,
564
587
  **listener_method_kwargs,
565
588
  ),
@@ -569,16 +592,16 @@ class Listener(AsyncEventLoopMixin):
569
592
 
570
593
  self._listeners_tasks_and_futures.append(listener_task_or_future)
571
594
  listener_task_or_future.add_done_callback(
572
- partial(self._on_listener_done_executing, listener_message_meta)
595
+ partial(self._on_listener_done_executing, listener_message_metadata)
573
596
  )
574
597
 
575
598
  def _parse_args(
576
- self, listener_message_meta: ListenerMessageMeta
599
+ self, listener_message_metadata: ListenerMessageMetadata
577
600
  ) -> tuple[list[Any], dict[str, Any]]:
578
601
  try:
579
- message = from_json(listener_message_meta.body)
602
+ message = from_json(listener_message_metadata.body)
580
603
  except:
581
- message = listener_message_meta.body.decode()
604
+ message = listener_message_metadata.body.decode()
582
605
 
583
606
  assigned_args: list[Any] = []
584
607
  listener_method_args = []
@@ -593,8 +616,8 @@ class Listener(AsyncEventLoopMixin):
593
616
  for (
594
617
  parameter_name,
595
618
  parameter,
596
- ) in listener_message_meta.listener_method_container.parameters.items():
597
- dependency_key = listener_message_meta.listener_method_container.dependencies.get(
619
+ ) in listener_message_metadata.listener_method_container.parameters.items():
620
+ dependency_key = listener_message_metadata.listener_method_container.dependencies.get(
598
621
  parameter_name
599
622
  )
600
623
  dependency = None
@@ -609,8 +632,8 @@ class Listener(AsyncEventLoopMixin):
609
632
  ):
610
633
  listener_context = ListenerContext(
611
634
  queue=self._queue,
612
- listener_name=listener_message_meta.listener_name,
613
- body=listener_message_meta.body,
635
+ listener_name=listener_message_metadata.listener_name,
636
+ body=listener_message_metadata.body,
614
637
  flow_control=self._flow_control,
615
638
  )
616
639
 
@@ -709,8 +732,8 @@ class Listener(AsyncEventLoopMixin):
709
732
  for (
710
733
  parameter_name,
711
734
  parameter,
712
- ) in listener_message_meta.listener_method_container.parameters.items():
713
- dependency = listener_message_meta.listener_method_container.dependencies.get(
735
+ ) in listener_message_metadata.listener_method_container.parameters.items():
736
+ dependency = listener_message_metadata.listener_method_container.dependencies.get(
714
737
  parameter_name
715
738
  )
716
739
 
@@ -721,8 +744,8 @@ class Listener(AsyncEventLoopMixin):
721
744
  listener_method_args.append(
722
745
  ListenerContext(
723
746
  queue=self._queue,
724
- listener_name=listener_message_meta.listener_name,
725
- body=listener_message_meta.body,
747
+ listener_name=listener_message_metadata.listener_name,
748
+ body=listener_message_metadata.body,
726
749
  flow_control=self._flow_control,
727
750
  )
728
751
  )
@@ -773,7 +796,7 @@ class Listener(AsyncEventLoopMixin):
773
796
 
774
797
  def _on_listener_done_executing(
775
798
  self,
776
- listener_message_meta: ListenerMessageMeta,
799
+ listener_message_metadata: ListenerMessageMetadata,
777
800
  task_or_future: Task[Any] | Future[Any],
778
801
  ) -> None:
779
802
  if (
@@ -785,14 +808,14 @@ class Listener(AsyncEventLoopMixin):
785
808
  if task_or_future.cancelled():
786
809
  return
787
810
 
788
- self._observe_listener_time(listener_message_meta)
811
+ self._observe_listener_time(listener_message_metadata)
789
812
 
790
813
  exception = task_or_future.exception()
791
814
 
792
815
  if exception is not None:
793
- if listener_message_meta.properties.reply_to is None:
816
+ if listener_message_metadata.properties.reply_to is None:
794
817
  retry_policy = (
795
- listener_message_meta.listener_method_container.retry_policy
818
+ listener_message_metadata.listener_method_container.retry_policy
796
819
  or self._retry_policy
797
820
  or self._global_retry_policy
798
821
  )
@@ -812,12 +835,10 @@ class Listener(AsyncEventLoopMixin):
812
835
  ):
813
836
  times_rejected = None
814
837
 
815
- if listener_message_meta.properties.headers is not None:
838
+ if listener_message_metadata.properties.headers is not None:
816
839
  try:
817
- _times_rejected = (
818
- listener_message_meta.properties.headers.get(
819
- "times_rejected"
820
- )
840
+ _times_rejected = listener_message_metadata.properties.headers.get(
841
+ "times_rejected"
821
842
  )
822
843
 
823
844
  if isinstance(_times_rejected, int):
@@ -835,7 +856,7 @@ class Listener(AsyncEventLoopMixin):
835
856
 
836
857
  if retry_policy.can_retry(times_rejected):
837
858
  self._reject_message(
838
- listener_message_meta=listener_message_meta,
859
+ listener_message_metadata=listener_message_metadata,
839
860
  retry_policy=retry_policy,
840
861
  times_rejected=times_rejected,
841
862
  )
@@ -844,43 +865,44 @@ class Listener(AsyncEventLoopMixin):
844
865
 
845
866
  self._call_exception_callback(
846
867
  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}`",
868
+ listener_message_metadata=listener_message_metadata,
869
+ message=f"error occured while executing listener `{listener_message_metadata.listener_name}` in queue `{self._queue}`",
849
870
  )
850
871
 
851
872
  if (
852
- listener_message_meta.properties.reply_to is not None
873
+ listener_message_metadata.properties.reply_to is not None
853
874
  and exception is None
854
875
  ):
855
876
  self._reply_response(
856
- listener_message_meta=listener_message_meta,
877
+ listener_message_metadata=listener_message_metadata,
857
878
  response=task_or_future.result(),
858
879
  )
859
880
 
860
881
  self._logger.debug(
861
882
  "message from queue `%s` consumed by listener `%s`",
862
883
  self.queue,
863
- listener_message_meta.listener_name,
884
+ listener_message_metadata.listener_name,
864
885
  )
865
886
 
866
887
  if exception is not None:
867
- self.LISTENER_FAILED_COMSUMPTION.labels(
888
+ self._LISTENER_FAILED_COMSUMPTION.labels(
868
889
  queue=self._queue,
869
- listener_name=listener_message_meta.listener_name,
890
+ listener_name=listener_message_metadata.listener_name,
870
891
  exception=exception.__class__.__name__,
871
892
  ).inc()
872
893
  else:
873
- self.LISTENER_SUCCEEDED_COMSUMPTION.labels(
894
+ self._LISTENER_SUCCEEDED_COMSUMPTION.labels(
874
895
  queue=self._queue,
875
- listener_name=listener_message_meta.listener_name,
896
+ listener_name=listener_message_metadata.listener_name,
876
897
  ).inc()
877
898
 
878
899
  def _observe_listener_time(
879
- self, listener_message_meta: ListenerMessageMeta
900
+ self, listener_message_metadata: ListenerMessageMetadata
880
901
  ) -> 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())
902
+ self._LISTENER_PROCESSING_LATENCY.labels(
903
+ queue=self._queue,
904
+ listener_name=listener_message_metadata.listener_name,
905
+ ).observe(listener_message_metadata.listener_start_time - time())
884
906
 
885
907
  def _is_recoverable_exception(
886
908
  self,
@@ -905,7 +927,7 @@ class Listener(AsyncEventLoopMixin):
905
927
  ):
906
928
  return True
907
929
 
908
- cause = cause.__cause__ or exception.__context__
930
+ cause = cause.__cause__ or cause.__context__
909
931
 
910
932
  return False
911
933
 
@@ -922,84 +944,86 @@ class Listener(AsyncEventLoopMixin):
922
944
  def _call_exception_callback(
923
945
  self,
924
946
  exception: BaseException,
925
- listener_message_meta: ListenerMessageMeta,
947
+ listener_message_metadata: ListenerMessageMetadata,
926
948
  message: str | None = None,
927
949
  ) -> None:
928
950
  context_dispose_callback = None
929
951
  rpc_reply = None
952
+ tags = [
953
+ "rabbitmq",
954
+ self._queue,
955
+ listener_message_metadata.listener_name,
956
+ ]
957
+ extra = {
958
+ "serviceType": "rabbitmq",
959
+ "queue": self._queue,
960
+ "listenerName": listener_message_metadata.listener_name,
961
+ "section": "exceptionHandlerCallback",
962
+ "raisedException": exception.__class__.__name__,
963
+ }
930
964
 
931
- if listener_message_meta.properties.reply_to is not None:
965
+ if listener_message_metadata.properties.reply_to is not None:
932
966
 
933
967
  def on_context_disposed(context: ListenerContext) -> None:
934
- assert listener_message_meta.properties.reply_to is not None
968
+ assert listener_message_metadata.properties.reply_to is not None
935
969
  assert context.rpc_reply is not None
936
970
 
937
971
  if not context.rpc_reply.replied:
938
972
  self._reply_response(
939
- listener_message_meta=listener_message_meta,
973
+ listener_message_metadata=listener_message_metadata,
940
974
  response=self._reponse_from_exception(exception),
941
975
  )
942
976
 
943
977
  context_dispose_callback = on_context_disposed
944
978
  rpc_reply = RpcReply(
945
979
  channel_pool=self._channel_pool,
946
- reply_to=listener_message_meta.properties.reply_to,
947
- correlation_id=listener_message_meta.properties.correlation_id,
980
+ reply_to=listener_message_metadata.properties.reply_to,
981
+ blocked_connection_check_callback=self._blocked_connection_check_callback,
982
+ correlation_id=listener_message_metadata.properties.correlation_id,
948
983
  )
949
984
 
950
985
  try:
951
986
  exception_callback_succeeded = self._on_exception_callback(
952
987
  ListenerContext(
953
988
  queue=self._queue,
954
- listener_name=listener_message_meta.listener_name,
955
- body=listener_message_meta.body,
989
+ listener_name=listener_message_metadata.listener_name,
990
+ body=listener_message_metadata.body,
956
991
  flow_control=self._flow_control,
957
992
  rpc_reply=rpc_reply,
958
993
  context_dispose_callback=context_dispose_callback,
994
+ ).set_labels(
995
+ {
996
+ "queue": self._queue,
997
+ "listener_name": listener_message_metadata.listener_name,
998
+ "exception": exception.__class__.__name__,
999
+ }
959
1000
  ),
960
1001
  exception,
961
1002
  )
962
1003
  except:
1004
+ tags.append("exception_callback_error")
963
1005
  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
- },
1006
+ message=f"error occured while invoking rabbitmq exception handler callback in listener `{listener_message_metadata.listener_name}` and queue `{self._queue}`",
1007
+ tags=tags,
1008
+ extra=extra,
977
1009
  )
978
1010
 
979
1011
  return
980
1012
 
981
1013
  if not exception_callback_succeeded:
1014
+ tags.append("exception_callback_unsuccessful")
982
1015
  self._remote_logger.exception(
983
1016
  message=(
984
1017
  message
985
- or f"error occured while handling event in listener `{listener_message_meta.listener_name}` and queue `{self._queue}`"
1018
+ or f"error occured while handling event in listener `{listener_message_metadata.listener_name}` and queue `{self._queue}`"
986
1019
  ),
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
- },
1020
+ tags=tags,
1021
+ extra=extra,
998
1022
  )
999
1023
 
1000
1024
  def _reject_message(
1001
1025
  self,
1002
- listener_message_meta: ListenerMessageMeta,
1026
+ listener_message_metadata: ListenerMessageMetadata,
1003
1027
  retry_policy: RetryPolicy,
1004
1028
  times_rejected: int,
1005
1029
  ) -> None:
@@ -1007,7 +1031,7 @@ class Listener(AsyncEventLoopMixin):
1007
1031
 
1008
1032
  self._logger.debug(
1009
1033
  "message will be redelivered to listenr `%s` on queue `%s` after `%f` seconds, times redelivered `%d`",
1010
- listener_message_meta.listener_name,
1034
+ listener_message_metadata.listener_name,
1011
1035
  self._queue,
1012
1036
  message_redelivery_delay,
1013
1037
  times_rejected,
@@ -1016,27 +1040,27 @@ class Listener(AsyncEventLoopMixin):
1016
1040
  delay=message_redelivery_delay,
1017
1041
  callback=partial(
1018
1042
  self._on_time_to_redeliver_message,
1019
- listener_message_meta,
1043
+ listener_message_metadata,
1020
1044
  times_rejected,
1021
1045
  ),
1022
1046
  )
1023
1047
 
1024
1048
  def _on_time_to_redeliver_message(
1025
1049
  self,
1026
- listener_message_meta: ListenerMessageMeta,
1050
+ listener_message_metadata: ListenerMessageMetadata,
1027
1051
  times_rejected: int,
1028
1052
  ) -> None:
1029
1053
  self.loop.create_task(self._channel_pool.get()).add_done_callback(
1030
1054
  partial(
1031
1055
  self._on_redelivery_channel_found,
1032
- listener_message_meta,
1056
+ listener_message_metadata,
1033
1057
  times_rejected,
1034
1058
  )
1035
1059
  )
1036
1060
 
1037
1061
  def _on_redelivery_channel_found(
1038
1062
  self,
1039
- listener_message_meta: ListenerMessageMeta,
1063
+ listener_message_metadata: ListenerMessageMetadata,
1040
1064
  times_rejected: int,
1041
1065
  task: Task[BaseChannel],
1042
1066
  ) -> None:
@@ -1048,66 +1072,84 @@ class Listener(AsyncEventLoopMixin):
1048
1072
  if exception is not None:
1049
1073
  self._call_exception_callback(
1050
1074
  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",
1075
+ listener_message_metadata=listener_message_metadata,
1076
+ 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
1077
  )
1054
1078
 
1055
1079
  return
1056
1080
 
1057
1081
  headers = {
1058
- self._listener_name_header_key: listener_message_meta.listener_name,
1082
+ self._listener_name_header_key: listener_message_metadata.listener_name,
1059
1083
  "times_rejected": times_rejected + 1,
1060
1084
  }
1061
1085
 
1062
- if listener_message_meta.properties.headers is None:
1063
- listener_message_meta.properties.headers = headers
1086
+ if listener_message_metadata.properties.headers is None:
1087
+ listener_message_metadata.properties.headers = headers
1064
1088
  else:
1065
1089
  cast(
1066
- dict[str, Any], listener_message_meta.properties.headers
1090
+ dict[str, Any], listener_message_metadata.properties.headers
1067
1091
  ).update(headers)
1068
1092
 
1093
+ if self._blocked_connection_check_callback():
1094
+ self._remote_logger.error(
1095
+ message=f"couldn't redeliver message to queue `{self._queue}` and listener `{listener_message_metadata.listener_name}` due to blocked connection",
1096
+ tags=[
1097
+ "rabbitmq",
1098
+ "redelivery_connection_blocked",
1099
+ self._queue,
1100
+ listener_message_metadata.listener_name,
1101
+ ],
1102
+ extra={
1103
+ "serviceType": "rabbitmq",
1104
+ "queue": self._queue,
1105
+ "listenerName": listener_message_metadata.listener_name,
1106
+ },
1107
+ )
1108
+
1109
+ return
1110
+
1069
1111
  try:
1070
1112
  with task.result() as channel:
1071
1113
  channel.basic_publish(
1072
1114
  exchange=DEFAULT_EXCHANGE,
1073
1115
  routing_key=self._queue,
1074
- body=listener_message_meta.body,
1075
- properties=listener_message_meta.properties,
1116
+ body=listener_message_metadata.body,
1117
+ properties=listener_message_metadata.properties,
1076
1118
  )
1077
1119
  except Exception as e:
1078
1120
  self._call_exception_callback(
1079
1121
  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",
1122
+ listener_message_metadata=listener_message_metadata,
1123
+ 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
1124
  )
1083
1125
 
1084
1126
  return
1085
1127
 
1086
1128
  self._logger.debug(
1087
1129
  "message queued for redelivery to `%s` on queue `%s`, times redelivered `%d`",
1088
- listener_message_meta.listener_name,
1130
+ listener_message_metadata.listener_name,
1089
1131
  self._queue,
1090
1132
  times_rejected + 1,
1091
1133
  )
1092
1134
 
1093
1135
  def _reply_response(
1094
1136
  self,
1095
- listener_message_meta: ListenerMessageMeta,
1137
+ listener_message_metadata: ListenerMessageMetadata,
1096
1138
  response: Any,
1097
1139
  ) -> None:
1098
- assert listener_message_meta.properties.reply_to is not None
1140
+ assert listener_message_metadata.properties.reply_to is not None
1099
1141
 
1100
1142
  reponse_properties = BasicProperties(content_type="application/json")
1101
1143
 
1102
- if listener_message_meta.properties.correlation_id is None:
1144
+ if listener_message_metadata.properties.correlation_id is None:
1103
1145
  self._logger.warning(
1104
1146
  "`correlation_id` property not supplied for listener `%s` and queue `%s`",
1105
- listener_message_meta.listener_name,
1147
+ listener_message_metadata.listener_name,
1106
1148
  self._queue,
1107
1149
  )
1108
1150
  else:
1109
1151
  reponse_properties.correlation_id = (
1110
- listener_message_meta.properties.correlation_id
1152
+ listener_message_metadata.properties.correlation_id
1111
1153
  )
1112
1154
 
1113
1155
  try:
@@ -1115,8 +1157,8 @@ class Listener(AsyncEventLoopMixin):
1115
1157
  except Exception as e:
1116
1158
  self._call_exception_callback(
1117
1159
  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}`",
1160
+ listener_message_metadata=listener_message_metadata,
1161
+ message=f"listener response is not json serializable in listener `{listener_message_metadata.listener_name}` and queue `{self._queue}`",
1120
1162
  )
1121
1163
 
1122
1164
  return
@@ -1124,7 +1166,7 @@ class Listener(AsyncEventLoopMixin):
1124
1166
  self.loop.create_task(self._channel_pool.get()).add_done_callback(
1125
1167
  partial(
1126
1168
  self._on_reply_channel_found,
1127
- listener_message_meta,
1169
+ listener_message_metadata,
1128
1170
  response_body,
1129
1171
  reponse_properties,
1130
1172
  )
@@ -1156,7 +1198,7 @@ class Listener(AsyncEventLoopMixin):
1156
1198
 
1157
1199
  def _on_reply_channel_found(
1158
1200
  self,
1159
- listener_message_meta: ListenerMessageMeta,
1201
+ listener_message_metadata: ListenerMessageMetadata,
1160
1202
  response_body: bytes,
1161
1203
  response_properties: BasicProperties,
1162
1204
  task: Task[BaseChannel],
@@ -1169,36 +1211,54 @@ class Listener(AsyncEventLoopMixin):
1169
1211
  if exception is not None:
1170
1212
  self._call_exception_callback(
1171
1213
  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}`",
1214
+ listener_message_metadata=listener_message_metadata,
1215
+ message=f"error occured while getting channel for publishing response in listener `{listener_message_metadata.listener_name}` and queue `{self._queue}`",
1174
1216
  )
1175
1217
 
1176
1218
  return
1177
1219
 
1178
- try:
1179
- assert listener_message_meta.properties.reply_to is not None
1220
+ if self._blocked_connection_check_callback():
1221
+ self._remote_logger.error(
1222
+ message=f"couldn't repond to rpc call on queue `{self._queue}` and listener `{listener_message_metadata.listener_name}` due to blocked connection",
1223
+ tags=[
1224
+ "rabbitmq",
1225
+ "reply_connection_blocked",
1226
+ self._queue,
1227
+ listener_message_metadata.listener_name,
1228
+ ],
1229
+ extra={
1230
+ "serviceType": "rabbitmq",
1231
+ "queue": self._queue,
1232
+ "listenerName": listener_message_metadata.listener_name,
1233
+ },
1234
+ )
1235
+
1236
+ return
1180
1237
 
1238
+ assert listener_message_metadata.properties.reply_to is not None
1239
+
1240
+ try:
1181
1241
  with task.result() as channel:
1182
1242
  channel.basic_publish(
1183
1243
  exchange=DEFAULT_EXCHANGE,
1184
- routing_key=listener_message_meta.properties.reply_to,
1244
+ routing_key=listener_message_metadata.properties.reply_to,
1185
1245
  properties=response_properties,
1186
1246
  body=response_body,
1187
1247
  )
1188
1248
  except Exception as e:
1189
1249
  self._call_exception_callback(
1190
1250
  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}`",
1251
+ listener_message_metadata=listener_message_metadata,
1252
+ message=f"error occured while publishing response to rpc call in listener `{listener_message_metadata.listener_name}` and queue `{self._queue}`",
1193
1253
  )
1194
1254
 
1195
1255
  return
1196
1256
 
1197
1257
  self._logger.debug(
1198
1258
  "sent a reply to `%s` for request from queue `%s` and listener `%s`",
1199
- listener_message_meta.properties.reply_to,
1259
+ listener_message_metadata.properties.reply_to,
1200
1260
  self._queue,
1201
- listener_message_meta.listener_name,
1261
+ listener_message_metadata.listener_name,
1202
1262
  )
1203
1263
 
1204
1264
  def __call__(self, listener: type[L]) -> type[L]:
@@ -1207,6 +1267,13 @@ class Listener(AsyncEventLoopMixin):
1207
1267
  return listener
1208
1268
 
1209
1269
 
1270
+ @dataclass
1271
+ class ListenerMethodMetadata:
1272
+ listener_type: type[Listener]
1273
+ listener_name: str | None = None
1274
+ retry_policy: RetryPolicy | None = None
1275
+
1276
+
1210
1277
  class Consumer(Listener):
1211
1278
  def __init__(
1212
1279
  self,
@@ -1266,7 +1333,11 @@ def consume(
1266
1333
  setattr(
1267
1334
  consumer_method,
1268
1335
  CONSUMER_ATTRIBUTE,
1269
- ListenerMethodMeta(listener_name=target, retry_policy=retry_policy),
1336
+ ListenerMethodMetadata(
1337
+ listener_type=Consumer,
1338
+ listener_name=target,
1339
+ retry_policy=retry_policy,
1340
+ ),
1270
1341
  )
1271
1342
 
1272
1343
  return consumer_method
@@ -1317,7 +1388,11 @@ def execute(
1317
1388
  )
1318
1389
 
1319
1390
  setattr(
1320
- worker_method, RPC_WORKER_ATTRIBUTE, ListenerMethodMeta(procedure)
1391
+ worker_method,
1392
+ RPC_WORKER_ATTRIBUTE,
1393
+ ListenerMethodMetadata(
1394
+ listener_type=RpcWorker, listener_name=procedure
1395
+ ),
1321
1396
  )
1322
1397
 
1323
1398
  return worker_method
@@ -1325,13 +1400,9 @@ def execute(
1325
1400
  return wrapper
1326
1401
 
1327
1402
 
1328
- @dataclass
1329
- class ListenerMethodMeta:
1330
- listener_name: str | None = None
1331
- retry_policy: RetryPolicy | None = None
1332
-
1333
-
1334
1403
  class ListenerBase:
1404
+ _LISTENER_ATTRIBUTES = {CONSUMER_ATTRIBUTE, RPC_WORKER_ATTRIBUTE}
1405
+
1335
1406
  def get_inner_listener(self) -> Listener:
1336
1407
  listener = getattr(self, LISTENER_ATTRIBUTE, None)
1337
1408
 
@@ -1352,32 +1423,35 @@ class ListenerBase:
1352
1423
  if attribute is None:
1353
1424
  continue
1354
1425
 
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
- )
1426
+ listener_method_attribute = None
1427
+ listener_method = None
1428
+ listener_method_metadata = None
1429
+
1430
+ for listener_attribute in self._LISTENER_ATTRIBUTES:
1431
+ if (
1432
+ listener_method is not None
1433
+ and listener_method_metadata is not None
1434
+ ):
1435
+ break
1362
1436
 
1363
- if listener_method is None or listener_method_meta is None:
1364
1437
  (
1365
1438
  listener_method_attribute,
1366
1439
  listener_method,
1367
- listener_method_meta,
1440
+ listener_method_metadata,
1368
1441
  ) = self._validate_listener_method_attribute(
1369
1442
  attribute=attribute,
1370
- listener_method_attribute=RPC_WORKER_ATTRIBUTE,
1443
+ listener_method_attribute=listener_attribute,
1371
1444
  previous_listener_method_attribute=listener_method_attribute,
1445
+ listener=listener,
1372
1446
  )
1373
1447
 
1374
- if listener_method is None or listener_method_meta is None:
1448
+ if listener_method is None or listener_method_metadata is None:
1375
1449
  continue
1376
1450
 
1377
1451
  listener.add_listener_method(
1378
- listener_name=listener_method_meta.listener_name,
1452
+ listener_name=listener_method_metadata.listener_name,
1379
1453
  listener_method=listener_method,
1380
- retry_policy=listener_method_meta.retry_policy,
1454
+ retry_policy=listener_method_metadata.retry_policy,
1381
1455
  )
1382
1456
 
1383
1457
  return listener
@@ -1387,19 +1461,21 @@ class ListenerBase:
1387
1461
  attribute: Any,
1388
1462
  listener_method_attribute: str,
1389
1463
  previous_listener_method_attribute: str | None,
1464
+ listener: Listener,
1390
1465
  ) -> tuple[
1391
- str | None, Callable[..., Any] | None, ListenerMethodMeta | None
1466
+ str | None, Callable[..., Any] | None, ListenerMethodMetadata | None
1392
1467
  ]:
1393
- listener_method_meta = getattr(
1468
+ listener_method_metadata = getattr(
1394
1469
  attribute, listener_method_attribute, None
1395
1470
  )
1396
1471
 
1397
- if listener_method_meta is None:
1472
+ if listener_method_metadata is None:
1398
1473
  return previous_listener_method_attribute or None, None, None
1399
1474
 
1400
- if not isinstance(listener_method_meta, ListenerMethodMeta):
1475
+ if not isinstance(listener_method_metadata, ListenerMethodMetadata):
1401
1476
  raise TypeError(
1402
- f"expected `{listener_method_attribute}` to by of type `ListenerMethodMeta`, got {type(listener_method_meta)}"
1477
+ f"expected `{listener_method_attribute}` to by of type `ListenerMethodMetadata`, "
1478
+ f"got {type(listener_method_metadata)}"
1403
1479
  )
1404
1480
 
1405
1481
  if (
@@ -1413,4 +1489,17 @@ class ListenerBase:
1413
1489
  f"object annotated with `{listener_method_attribute}` is not callable"
1414
1490
  )
1415
1491
 
1416
- return listener_method_attribute, attribute, listener_method_meta
1492
+ if (
1493
+ listener_method_metadata.listener_type
1494
+ not in listener.__class__.mro()
1495
+ ):
1496
+ listener_mro = ", ".join(
1497
+ map(lambda c: c.__name__, listener.__class__.mro())
1498
+ )
1499
+
1500
+ raise TypeError(
1501
+ f"listener method is not correct member of `{listener_method_metadata.listener_type.__name__}`, "
1502
+ f"got `[{listener_mro}]` super classes"
1503
+ )
1504
+
1505
+ return listener_method_attribute, attribute, listener_method_metadata