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.
- qena_shared_lib/__init__.py +20 -2
- qena_shared_lib/alias.py +27 -0
- qena_shared_lib/application.py +4 -4
- qena_shared_lib/background.py +9 -7
- qena_shared_lib/cache.py +61 -0
- qena_shared_lib/enums.py +8 -0
- qena_shared_lib/eventbus.py +373 -0
- qena_shared_lib/exception_handling.py +409 -0
- qena_shared_lib/exceptions.py +167 -57
- qena_shared_lib/http/__init__.py +110 -0
- qena_shared_lib/{http.py → http/_base.py} +36 -36
- qena_shared_lib/http/_exception_handlers.py +202 -0
- qena_shared_lib/http/_request.py +24 -0
- qena_shared_lib/http/_response.py +24 -0
- qena_shared_lib/kafka/__init__.py +21 -0
- qena_shared_lib/kafka/_base.py +233 -0
- qena_shared_lib/kafka/_consumer.py +597 -0
- qena_shared_lib/kafka/_exception_handlers.py +124 -0
- qena_shared_lib/kafka/_producer.py +133 -0
- qena_shared_lib/logging.py +17 -13
- qena_shared_lib/mongodb.py +575 -0
- qena_shared_lib/rabbitmq/__init__.py +6 -6
- qena_shared_lib/rabbitmq/_base.py +68 -132
- qena_shared_lib/rabbitmq/_channel.py +2 -4
- qena_shared_lib/rabbitmq/_exception_handlers.py +69 -142
- qena_shared_lib/rabbitmq/_listener.py +245 -180
- qena_shared_lib/rabbitmq/_publisher.py +5 -5
- qena_shared_lib/rabbitmq/_rpc_client.py +21 -22
- qena_shared_lib/rabbitmq/message/__init__.py +19 -0
- qena_shared_lib/rabbitmq/message/_inbound.py +13 -0
- qena_shared_lib/rabbitmq/message/_outbound.py +13 -0
- qena_shared_lib/redis.py +47 -0
- qena_shared_lib/remotelogging/_base.py +34 -28
- qena_shared_lib/remotelogging/logstash/_base.py +3 -2
- qena_shared_lib/remotelogging/logstash/_http_sender.py +2 -4
- qena_shared_lib/remotelogging/logstash/_tcp_sender.py +2 -2
- qena_shared_lib/scheduler.py +24 -15
- qena_shared_lib/security.py +39 -32
- qena_shared_lib/sync.py +91 -0
- qena_shared_lib/utils.py +13 -11
- {qena_shared_lib-0.1.17.dist-info → qena_shared_lib-0.1.19.dist-info}/METADATA +395 -32
- qena_shared_lib-0.1.19.dist-info/RECORD +50 -0
- qena_shared_lib-0.1.19.dist-info/WHEEL +4 -0
- qena_shared_lib/exception_handlers.py +0 -235
- qena_shared_lib-0.1.17.dist-info/RECORD +0 -31
- 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 ..
|
|
34
|
-
from ..
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
"
|
|
508
|
+
"rabbitmq",
|
|
509
|
+
"listener_doesnt_exist",
|
|
489
510
|
self._queue,
|
|
490
511
|
listener_name,
|
|
491
512
|
],
|
|
492
513
|
extra={
|
|
493
|
-
"serviceType": "
|
|
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
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
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,
|
|
534
|
+
self, listener_message_metadata: ListenerMessageMetadata
|
|
534
535
|
) -> None:
|
|
535
536
|
try:
|
|
536
537
|
listener_method_args, listener_method_kwargs = self._parse_args(
|
|
537
|
-
|
|
538
|
+
listener_message_metadata
|
|
538
539
|
)
|
|
539
540
|
except Exception as e:
|
|
540
541
|
self._call_exception_callback(
|
|
541
542
|
exception=e,
|
|
542
|
-
|
|
543
|
-
message=f"arguments for listener `{
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
571
|
+
partial(self._on_listener_done_executing, listener_message_metadata)
|
|
573
572
|
)
|
|
574
573
|
|
|
575
574
|
def _parse_args(
|
|
576
|
-
self,
|
|
575
|
+
self, listener_message_metadata: ListenerMessageMetadata
|
|
577
576
|
) -> tuple[list[Any], dict[str, Any]]:
|
|
578
577
|
try:
|
|
579
|
-
message = from_json(
|
|
578
|
+
message = from_json(listener_message_metadata.body)
|
|
580
579
|
except:
|
|
581
|
-
message =
|
|
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
|
|
597
|
-
dependency_key =
|
|
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=
|
|
613
|
-
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
|
|
713
|
-
dependency =
|
|
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=
|
|
725
|
-
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
|
-
|
|
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(
|
|
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
|
|
792
|
+
if listener_message_metadata.properties.reply_to is None:
|
|
794
793
|
retry_policy = (
|
|
795
|
-
|
|
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
|
|
814
|
+
if listener_message_metadata.properties.headers is not None:
|
|
816
815
|
try:
|
|
817
|
-
_times_rejected = (
|
|
818
|
-
|
|
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
|
-
|
|
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
|
-
|
|
848
|
-
message=f"error occured while executing listener `{
|
|
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
|
-
|
|
849
|
+
listener_message_metadata.properties.reply_to is not None
|
|
853
850
|
and exception is None
|
|
854
851
|
):
|
|
855
852
|
self._reply_response(
|
|
856
|
-
|
|
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
|
-
|
|
860
|
+
listener_message_metadata.listener_name,
|
|
864
861
|
)
|
|
865
862
|
|
|
866
863
|
if exception is not None:
|
|
867
|
-
self.
|
|
864
|
+
self._LISTENER_FAILED_COMSUMPTION.labels(
|
|
868
865
|
queue=self._queue,
|
|
869
|
-
listener_name=
|
|
866
|
+
listener_name=listener_message_metadata.listener_name,
|
|
870
867
|
exception=exception.__class__.__name__,
|
|
871
868
|
).inc()
|
|
872
869
|
else:
|
|
873
|
-
self.
|
|
870
|
+
self._LISTENER_SUCCEEDED_COMSUMPTION.labels(
|
|
874
871
|
queue=self._queue,
|
|
875
|
-
listener_name=
|
|
872
|
+
listener_name=listener_message_metadata.listener_name,
|
|
876
873
|
).inc()
|
|
877
874
|
|
|
878
875
|
def _observe_listener_time(
|
|
879
|
-
self,
|
|
876
|
+
self, listener_message_metadata: ListenerMessageMetadata
|
|
880
877
|
) -> None:
|
|
881
|
-
self.
|
|
882
|
-
queue=self._queue,
|
|
883
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
941
|
+
if listener_message_metadata.properties.reply_to is not None:
|
|
932
942
|
|
|
933
943
|
def on_context_disposed(context: ListenerContext) -> None:
|
|
934
|
-
assert
|
|
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
|
-
|
|
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=
|
|
947
|
-
|
|
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=
|
|
955
|
-
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 `{
|
|
965
|
-
tags=
|
|
966
|
-
|
|
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 `{
|
|
994
|
+
or f"error occured while handling event in listener `{listener_message_metadata.listener_name}` and queue `{self._queue}`"
|
|
986
995
|
),
|
|
987
|
-
tags=
|
|
988
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1052
|
-
message=f"error occured while getting channel from pool in listener `{
|
|
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:
|
|
1058
|
+
self._listener_name_header_key: listener_message_metadata.listener_name,
|
|
1059
1059
|
"times_rejected": times_rejected + 1,
|
|
1060
1060
|
}
|
|
1061
1061
|
|
|
1062
|
-
if
|
|
1063
|
-
|
|
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],
|
|
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=
|
|
1075
|
-
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
|
-
|
|
1081
|
-
message=f"error occured while sending event for redelivery in listener `{
|
|
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
|
-
|
|
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
|
-
|
|
1113
|
+
listener_message_metadata: ListenerMessageMetadata,
|
|
1096
1114
|
response: Any,
|
|
1097
1115
|
) -> None:
|
|
1098
|
-
assert
|
|
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
|
|
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
|
-
|
|
1123
|
+
listener_message_metadata.listener_name,
|
|
1106
1124
|
self._queue,
|
|
1107
1125
|
)
|
|
1108
1126
|
else:
|
|
1109
1127
|
reponse_properties.correlation_id = (
|
|
1110
|
-
|
|
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
|
-
|
|
1119
|
-
message=f"listener response is not json serializable in listener `{
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1173
|
-
message=f"error occured while getting channel for publishing response in listener `{
|
|
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
|
-
|
|
1179
|
-
|
|
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=
|
|
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
|
-
|
|
1192
|
-
message=f"error occured while publishing response to rpc call in listener `{
|
|
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
|
-
|
|
1235
|
+
listener_message_metadata.properties.reply_to,
|
|
1200
1236
|
self._queue,
|
|
1201
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
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
|
-
|
|
1416
|
+
listener_method_metadata,
|
|
1368
1417
|
) = self._validate_listener_method_attribute(
|
|
1369
1418
|
attribute=attribute,
|
|
1370
|
-
listener_method_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
|
|
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=
|
|
1428
|
+
listener_name=listener_method_metadata.listener_name,
|
|
1379
1429
|
listener_method=listener_method,
|
|
1380
|
-
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,
|
|
1442
|
+
str | None, Callable[..., Any] | None, ListenerMethodMetadata | None
|
|
1392
1443
|
]:
|
|
1393
|
-
|
|
1444
|
+
listener_method_metadata = getattr(
|
|
1394
1445
|
attribute, listener_method_attribute, None
|
|
1395
1446
|
)
|
|
1396
1447
|
|
|
1397
|
-
if
|
|
1448
|
+
if listener_method_metadata is None:
|
|
1398
1449
|
return previous_listener_method_attribute or None, None, None
|
|
1399
1450
|
|
|
1400
|
-
if not isinstance(
|
|
1451
|
+
if not isinstance(listener_method_metadata, ListenerMethodMetadata):
|
|
1401
1452
|
raise TypeError(
|
|
1402
|
-
f"expected `{listener_method_attribute}` to by of type `
|
|
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
|
-
|
|
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
|