qena-shared-lib 0.1.7__py3-none-any.whl → 0.1.9__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/application.py +2 -2
- qena_shared_lib/exception_handlers.py +69 -44
- qena_shared_lib/exceptions.py +187 -85
- qena_shared_lib/http.py +20 -20
- qena_shared_lib/logstash/_base.py +2 -2
- qena_shared_lib/rabbitmq/__init__.py +6 -6
- qena_shared_lib/rabbitmq/_base.py +20 -17
- qena_shared_lib/rabbitmq/_exception_handlers.py +58 -71
- qena_shared_lib/rabbitmq/_listener.py +109 -42
- qena_shared_lib/rabbitmq/_publisher.py +2 -1
- qena_shared_lib/rabbitmq/_rpc_client.py +13 -6
- qena_shared_lib/scheduler.py +5 -0
- qena_shared_lib/security.py +3 -3
- {qena_shared_lib-0.1.7.dist-info → qena_shared_lib-0.1.9.dist-info}/METADATA +155 -22
- qena_shared_lib-0.1.9.dist-info/RECORD +29 -0
- qena_shared_lib/rabbitmq/_exceptions.py +0 -46
- qena_shared_lib-0.1.7.dist-info/RECORD +0 -30
- {qena_shared_lib-0.1.7.dist-info → qena_shared_lib-0.1.9.dist-info}/WHEEL +0 -0
@@ -27,17 +27,18 @@ from punq import Container, Scope
|
|
27
27
|
from pydantic import ValidationError
|
28
28
|
|
29
29
|
from ..dependencies.miscellaneous import validate_annotation
|
30
|
-
from ..exceptions import
|
30
|
+
from ..exceptions import (
|
31
|
+
RabbitMQConnectionUnhealthyError,
|
32
|
+
ServiceException,
|
33
|
+
)
|
31
34
|
from ..logging import LoggerProvider
|
32
35
|
from ..logstash import BaseLogstashSender
|
33
36
|
from ..utils import AsyncEventLoopMixin
|
34
37
|
from ._exception_handlers import (
|
35
38
|
handle_general_mq_exception,
|
36
|
-
|
37
|
-
handle_rabbitmq_exception,
|
39
|
+
handle_rabbit_mq_service_exception,
|
38
40
|
handle_validation_error,
|
39
41
|
)
|
40
|
-
from ._exceptions import RabbitMQException
|
41
42
|
from ._listener import (
|
42
43
|
LISTENER_ATTRIBUTE,
|
43
44
|
Listener,
|
@@ -121,9 +122,10 @@ class RabbitMqManager(AsyncEventLoopMixin):
|
|
121
122
|
type[Exception], ExceptionHandlerContainer
|
122
123
|
] = {}
|
123
124
|
|
124
|
-
self.exception_handler(
|
125
|
+
self.exception_handler(ServiceException)(
|
126
|
+
handle_rabbit_mq_service_exception
|
127
|
+
)
|
125
128
|
self.exception_handler(ValidationError)(handle_validation_error)
|
126
|
-
self.exception_handler(ServiceException)(handle_microservice_exception)
|
127
129
|
self.exception_handler(Exception)(handle_general_mq_exception)
|
128
130
|
|
129
131
|
self._channel_pool = ChannelPool()
|
@@ -288,7 +290,7 @@ class RabbitMqManager(AsyncEventLoopMixin):
|
|
288
290
|
@property
|
289
291
|
def connection(self) -> AsyncioConnection:
|
290
292
|
if not self._is_connection_healthy():
|
291
|
-
raise
|
293
|
+
raise RabbitMQConnectionUnhealthyError("connection not ready yet")
|
292
294
|
|
293
295
|
assert self._connection is not None
|
294
296
|
|
@@ -302,7 +304,9 @@ class RabbitMqManager(AsyncEventLoopMixin):
|
|
302
304
|
headers: dict[str, str] | None = None,
|
303
305
|
) -> Publisher:
|
304
306
|
if not self._is_connection_healthy():
|
305
|
-
raise
|
307
|
+
raise RabbitMQConnectionUnhealthyError(
|
308
|
+
"rabbitmq connection is not healthy"
|
309
|
+
)
|
306
310
|
|
307
311
|
return Publisher(
|
308
312
|
routing_key=routing_key,
|
@@ -322,16 +326,15 @@ class RabbitMqManager(AsyncEventLoopMixin):
|
|
322
326
|
return_type: type | None = None,
|
323
327
|
timeout: float = 0,
|
324
328
|
) -> RpcClient:
|
325
|
-
if timeout < 0:
|
326
|
-
raise ValueError(f"timeout cannot below 0, got {timeout} seconds")
|
327
|
-
|
328
329
|
if timeout == 0:
|
329
330
|
self._logger.warning(
|
330
331
|
"rpc call with 0 seconds timeout may never return back"
|
331
332
|
)
|
332
333
|
|
333
334
|
if not self._is_connection_healthy():
|
334
|
-
raise
|
335
|
+
raise RabbitMQConnectionUnhealthyError(
|
336
|
+
"rabbitmq connection is not healthy"
|
337
|
+
)
|
335
338
|
|
336
339
|
return RpcClient(
|
337
340
|
routing_key=routing_key,
|
@@ -341,7 +344,7 @@ class RabbitMqManager(AsyncEventLoopMixin):
|
|
341
344
|
procedure=procedure,
|
342
345
|
headers=headers,
|
343
346
|
return_type=return_type,
|
344
|
-
timeout=timeout,
|
347
|
+
timeout=abs(timeout),
|
345
348
|
)
|
346
349
|
|
347
350
|
def _is_connection_healthy(self) -> bool:
|
@@ -359,7 +362,7 @@ class RabbitMqManager(AsyncEventLoopMixin):
|
|
359
362
|
self._disconnected = True
|
360
363
|
|
361
364
|
if self._connection is None:
|
362
|
-
raise
|
365
|
+
raise RabbitMQConnectionUnhealthyError("connection not ready yet")
|
363
366
|
|
364
367
|
if self._connection.is_closing or self._connection.is_closed:
|
365
368
|
self._logger.info("already disconnected from rabbitmq")
|
@@ -411,7 +414,7 @@ class RabbitMqManager(AsyncEventLoopMixin):
|
|
411
414
|
return
|
412
415
|
|
413
416
|
if self._connection is None:
|
414
|
-
raise
|
417
|
+
raise RabbitMQConnectionUnhealthyError("connection not ready yet")
|
415
418
|
|
416
419
|
self.loop.create_task(
|
417
420
|
self._channel_pool.fill(self._connection)
|
@@ -464,7 +467,7 @@ class RabbitMqManager(AsyncEventLoopMixin):
|
|
464
467
|
return
|
465
468
|
|
466
469
|
if self._connection is None:
|
467
|
-
raise
|
470
|
+
raise RabbitMQConnectionUnhealthyError("connection not ready yet")
|
468
471
|
|
469
472
|
channel_count = len(self._channel_pool)
|
470
473
|
|
@@ -537,7 +540,7 @@ class RabbitMqManager(AsyncEventLoopMixin):
|
|
537
540
|
self._connected = True
|
538
541
|
|
539
542
|
if self._connection is None:
|
540
|
-
raise
|
543
|
+
raise RabbitMQConnectionUnhealthyError("connection not ready yet")
|
541
544
|
|
542
545
|
params = self._connection.params
|
543
546
|
listener_count = len(self._listeners)
|
@@ -3,80 +3,26 @@ from typing import Annotated
|
|
3
3
|
from pydantic import ValidationError
|
4
4
|
|
5
5
|
from ..dependencies.miscellaneous import DependsOn
|
6
|
-
from ..exceptions import
|
6
|
+
from ..exceptions import (
|
7
|
+
HTTPServiceError,
|
8
|
+
RabbitMQServiceException,
|
9
|
+
ServiceException,
|
10
|
+
Severity,
|
11
|
+
)
|
7
12
|
from ..logging import LoggerProvider
|
8
13
|
from ..logstash._base import BaseLogstashSender
|
9
|
-
from ._exceptions import RabbitMQException
|
10
14
|
from ._listener import ListenerContext
|
11
15
|
|
12
16
|
__all__ = [
|
13
17
|
"handle_general_mq_exception",
|
14
|
-
"
|
15
|
-
"handle_rabbitmq_exception",
|
18
|
+
"handle_rabbit_mq_service_exception",
|
16
19
|
"handle_validation_error",
|
17
20
|
]
|
18
21
|
|
19
22
|
RABBITMQ_EXCEPTION_HANDLER_LOGGER_NAME = "rabbitmq.exception_handler"
|
20
23
|
|
21
24
|
|
22
|
-
def
|
23
|
-
context: ListenerContext,
|
24
|
-
exception: RabbitMQException,
|
25
|
-
logstash: Annotated[BaseLogstashSender, DependsOn(BaseLogstashSender)],
|
26
|
-
logger_provider: Annotated[LoggerProvider, DependsOn(LoggerProvider)],
|
27
|
-
):
|
28
|
-
logger = logger_provider.get_logger(RABBITMQ_EXCEPTION_HANDLER_LOGGER_NAME)
|
29
|
-
|
30
|
-
if not exception.logstash_logging:
|
31
|
-
logger.warning("%r", exception)
|
32
|
-
|
33
|
-
return
|
34
|
-
|
35
|
-
logstash.warning(
|
36
|
-
message=exception.message,
|
37
|
-
tags=exception.tags
|
38
|
-
or [
|
39
|
-
"RabbitMQ",
|
40
|
-
"RabbitMQException",
|
41
|
-
str(exception.code),
|
42
|
-
context.queue,
|
43
|
-
context.listener_name or "__default__",
|
44
|
-
],
|
45
|
-
extra=exception.extra
|
46
|
-
or {
|
47
|
-
"serviceType": "RabbitMQ",
|
48
|
-
"queue": context.queue,
|
49
|
-
"listenerName": context.listener_name,
|
50
|
-
"exception": "RabbitMQException",
|
51
|
-
},
|
52
|
-
exception=exception,
|
53
|
-
)
|
54
|
-
|
55
|
-
|
56
|
-
def handle_validation_error(
|
57
|
-
context: ListenerContext,
|
58
|
-
exception: ValidationError,
|
59
|
-
logstash: Annotated[BaseLogstashSender, DependsOn(BaseLogstashSender)],
|
60
|
-
):
|
61
|
-
logstash.error(
|
62
|
-
message=f"invalid rabbitmq request data at queue `{context.queue}` and listener `{context.listener_name}`",
|
63
|
-
tags=[
|
64
|
-
"RabbitMQ",
|
65
|
-
"ValidationError",
|
66
|
-
context.queue,
|
67
|
-
context.listener_name or "__default__",
|
68
|
-
],
|
69
|
-
extra={
|
70
|
-
"serviceType": "RabbitMQ",
|
71
|
-
"queue": context.queue,
|
72
|
-
"listenerName": context.listener_name,
|
73
|
-
"exception": "ValidationError",
|
74
|
-
},
|
75
|
-
exception=exception,
|
76
|
-
)
|
77
|
-
|
78
|
-
|
79
|
-
def handle_microservice_exception(
|
25
|
+
def handle_rabbit_mq_service_exception(
|
80
26
|
context: ListenerContext,
|
81
27
|
exception: ServiceException,
|
82
28
|
logstash: Annotated[BaseLogstashSender, DependsOn(BaseLogstashSender)],
|
@@ -85,14 +31,10 @@ def handle_microservice_exception(
|
|
85
31
|
logger = logger_provider.get_logger(RABBITMQ_EXCEPTION_HANDLER_LOGGER_NAME)
|
86
32
|
tags = [
|
87
33
|
"RabbitMQ",
|
88
|
-
type(exception).__name__,
|
89
34
|
context.queue,
|
90
35
|
context.listener_name or "__default__",
|
36
|
+
exception.__class__.__name__,
|
91
37
|
]
|
92
|
-
|
93
|
-
if exception.tags:
|
94
|
-
exception.tags.extend(tags)
|
95
|
-
|
96
38
|
extra = {
|
97
39
|
"serviceType": "RabbitMQ",
|
98
40
|
"queue": context.queue,
|
@@ -100,8 +42,30 @@ def handle_microservice_exception(
|
|
100
42
|
"exception": exception.__class__.__name__,
|
101
43
|
}
|
102
44
|
|
45
|
+
match exception:
|
46
|
+
case HTTPServiceError() as http_service_error:
|
47
|
+
if http_service_error.status_code is not None:
|
48
|
+
str_status_code = str(http_service_error.status_code)
|
49
|
+
extra["statusCode"] = str_status_code
|
50
|
+
|
51
|
+
tags.append(str_status_code)
|
52
|
+
|
53
|
+
if http_service_error.response_code is not None:
|
54
|
+
str_response_code = str(http_service_error.response_code)
|
55
|
+
extra["responseCode"] = str_response_code
|
56
|
+
|
57
|
+
tags.append(str_response_code)
|
58
|
+
case RabbitMQServiceException() as rabbitmq_service_exception:
|
59
|
+
str_error_code = str(rabbitmq_service_exception.code)
|
60
|
+
extra["code"] = str_error_code
|
61
|
+
|
62
|
+
tags.append(str_error_code)
|
63
|
+
|
64
|
+
if exception.tags:
|
65
|
+
tags.extend(exception.tags)
|
66
|
+
|
103
67
|
if exception.extra:
|
104
|
-
|
68
|
+
extra.update(exception.extra)
|
105
69
|
|
106
70
|
exc_info = (
|
107
71
|
(type(exception), exception, exception.__traceback__)
|
@@ -123,8 +87,8 @@ def handle_microservice_exception(
|
|
123
87
|
if exception.logstash_logging:
|
124
88
|
logstash_logger_method(
|
125
89
|
message=exception.message,
|
126
|
-
tags=
|
127
|
-
extra=
|
90
|
+
tags=tags,
|
91
|
+
extra=extra,
|
128
92
|
exception=exception if exception.extract_exc_info else None,
|
129
93
|
)
|
130
94
|
else:
|
@@ -137,6 +101,29 @@ def handle_microservice_exception(
|
|
137
101
|
)
|
138
102
|
|
139
103
|
|
104
|
+
def handle_validation_error(
|
105
|
+
context: ListenerContext,
|
106
|
+
exception: ValidationError,
|
107
|
+
logstash: Annotated[BaseLogstashSender, DependsOn(BaseLogstashSender)],
|
108
|
+
):
|
109
|
+
logstash.error(
|
110
|
+
message=f"invalid rabbitmq request data at queue `{context.queue}` and listener `{context.listener_name}`",
|
111
|
+
tags=[
|
112
|
+
"RabbitMQ",
|
113
|
+
context.queue,
|
114
|
+
context.listener_name or "__default__",
|
115
|
+
"ValidationError",
|
116
|
+
],
|
117
|
+
extra={
|
118
|
+
"serviceType": "RabbitMQ",
|
119
|
+
"queue": context.queue,
|
120
|
+
"listenerName": context.listener_name,
|
121
|
+
"exception": "ValidationError",
|
122
|
+
},
|
123
|
+
exception=exception,
|
124
|
+
)
|
125
|
+
|
126
|
+
|
140
127
|
def handle_general_mq_exception(
|
141
128
|
context: ListenerContext,
|
142
129
|
exception: Exception,
|
@@ -146,9 +133,9 @@ def handle_general_mq_exception(
|
|
146
133
|
message=f"something went wrong while consuming message on queue `{context.queue}` and listener `{context.listener_name}`",
|
147
134
|
tags=[
|
148
135
|
"RabbitMQ",
|
149
|
-
exception.__class__.__name__,
|
150
136
|
context.queue,
|
151
137
|
context.listener_name or "__default__",
|
138
|
+
exception.__class__.__name__,
|
152
139
|
],
|
153
140
|
extra={
|
154
141
|
"serviceType": "RabbitMQ",
|
@@ -19,11 +19,11 @@ from pydantic import ValidationError
|
|
19
19
|
from pydantic_core import from_json, to_json
|
20
20
|
|
21
21
|
from ..dependencies.miscellaneous import validate_annotation
|
22
|
+
from ..exceptions import RabbitMQServiceException
|
22
23
|
from ..logging import LoggerProvider
|
23
24
|
from ..logstash import BaseLogstashSender
|
24
25
|
from ..utils import AsyncEventLoopMixin, TypeAdapterCache
|
25
26
|
from ._channel import BaseChannel
|
26
|
-
from ._exceptions import RabbitMQException
|
27
27
|
from ._pool import ChannelPool
|
28
28
|
|
29
29
|
__all__ = [
|
@@ -184,6 +184,7 @@ class RetryPolicy:
|
|
184
184
|
max_retry: int
|
185
185
|
retry_delay_strategy: RetryDelayStrategy
|
186
186
|
retry_delay_jitter: RetryDelayJitter | None = None
|
187
|
+
match_by_cause: bool = False
|
187
188
|
|
188
189
|
def can_retry(self, times_rejected: int) -> bool:
|
189
190
|
return times_rejected < self.max_retry
|
@@ -756,9 +757,18 @@ class Listener(AsyncEventLoopMixin):
|
|
756
757
|
or self._global_retry_policy
|
757
758
|
)
|
758
759
|
|
759
|
-
if retry_policy is not None and
|
760
|
-
|
761
|
-
|
760
|
+
if retry_policy is not None and (
|
761
|
+
self._is_recoverable_exception(
|
762
|
+
exception=exception,
|
763
|
+
retry_policy_exceptions=retry_policy.exceptions,
|
764
|
+
)
|
765
|
+
or (
|
766
|
+
retry_policy.match_by_cause
|
767
|
+
and self._has_recoverable_cause(
|
768
|
+
exception=exception,
|
769
|
+
retry_policy_exceptions=retry_policy.exceptions,
|
770
|
+
)
|
771
|
+
)
|
762
772
|
):
|
763
773
|
times_rejected = None
|
764
774
|
|
@@ -824,6 +834,43 @@ class Listener(AsyncEventLoopMixin):
|
|
824
834
|
queue=self._queue, listener_name=listener_message_meta.listener_name
|
825
835
|
).observe(listener_message_meta.listener_start_time - time())
|
826
836
|
|
837
|
+
def _is_recoverable_exception(
|
838
|
+
self,
|
839
|
+
exception: BaseException,
|
840
|
+
retry_policy_exceptions: Collection[type[Exception]],
|
841
|
+
) -> bool:
|
842
|
+
return self._in_retry_policy_exceptions(
|
843
|
+
exception=exception, retry_policy_exceptions=retry_policy_exceptions
|
844
|
+
)
|
845
|
+
|
846
|
+
def _has_recoverable_cause(
|
847
|
+
self,
|
848
|
+
exception: BaseException,
|
849
|
+
retry_policy_exceptions: Collection[type[Exception]],
|
850
|
+
) -> bool:
|
851
|
+
cause = exception.__cause__ or exception.__context__
|
852
|
+
|
853
|
+
while cause is not None:
|
854
|
+
if self._in_retry_policy_exceptions(
|
855
|
+
exception=cause,
|
856
|
+
retry_policy_exceptions=retry_policy_exceptions,
|
857
|
+
):
|
858
|
+
return True
|
859
|
+
|
860
|
+
cause = cause.__cause__ or exception.__context__
|
861
|
+
|
862
|
+
return False
|
863
|
+
|
864
|
+
def _in_retry_policy_exceptions(
|
865
|
+
self,
|
866
|
+
exception: BaseException,
|
867
|
+
retry_policy_exceptions: Collection[type[Exception]],
|
868
|
+
) -> bool:
|
869
|
+
return any(
|
870
|
+
exception_type in retry_policy_exceptions
|
871
|
+
for exception_type in type(exception).mro()
|
872
|
+
)
|
873
|
+
|
827
874
|
def _call_exception_callback(
|
828
875
|
self,
|
829
876
|
exception: BaseException,
|
@@ -1035,20 +1082,24 @@ class Listener(AsyncEventLoopMixin):
|
|
1035
1082
|
|
1036
1083
|
def _reponse_from_exception(self, exception: BaseException) -> dict:
|
1037
1084
|
match exception:
|
1038
|
-
case
|
1085
|
+
case RabbitMQServiceException() as rabbitmq_exception:
|
1039
1086
|
code = rabbitmq_exception.code
|
1040
1087
|
message = rabbitmq_exception.message
|
1088
|
+
data = rabbitmq_exception.data
|
1041
1089
|
case ValidationError() as validation_error:
|
1042
1090
|
code = 0
|
1043
|
-
message = validation_error.
|
1091
|
+
message = validation_error.title
|
1092
|
+
data = validation_error.json()
|
1044
1093
|
case unknown_execption:
|
1045
1094
|
code = 0
|
1046
1095
|
message = str(unknown_execption)
|
1096
|
+
data = None
|
1047
1097
|
|
1048
1098
|
return {
|
1049
1099
|
"exception": True,
|
1050
1100
|
"code": code,
|
1051
1101
|
"message": message,
|
1102
|
+
"data": data,
|
1052
1103
|
}
|
1053
1104
|
|
1054
1105
|
def _on_reply_channel_found(
|
@@ -1137,6 +1188,38 @@ class Consumer(Listener):
|
|
1137
1188
|
return wrapper
|
1138
1189
|
|
1139
1190
|
|
1191
|
+
def consumer(
|
1192
|
+
queue: str,
|
1193
|
+
prefetch_count: int = 250,
|
1194
|
+
retry_policy: RetryPolicy | None = None,
|
1195
|
+
) -> Consumer:
|
1196
|
+
return Consumer(
|
1197
|
+
queue=queue,
|
1198
|
+
prefetch_count=prefetch_count,
|
1199
|
+
retry_policy=retry_policy,
|
1200
|
+
)
|
1201
|
+
|
1202
|
+
|
1203
|
+
def consume(
|
1204
|
+
target: str | None = None, retry_policy: RetryPolicy | None = None
|
1205
|
+
) -> Callable[[Callable], Callable]:
|
1206
|
+
def wrapper(consumer_method: Callable) -> Callable:
|
1207
|
+
if not callable(consumer_method):
|
1208
|
+
raise TypeError(
|
1209
|
+
f"consumer method argument not a callable, got {type(consumer_method)}"
|
1210
|
+
)
|
1211
|
+
|
1212
|
+
setattr(
|
1213
|
+
consumer_method,
|
1214
|
+
CONSUMER_ATTRIBUTE,
|
1215
|
+
ListenerMethodMeta(listener_name=target, retry_policy=retry_policy),
|
1216
|
+
)
|
1217
|
+
|
1218
|
+
return consumer_method
|
1219
|
+
|
1220
|
+
return wrapper
|
1221
|
+
|
1222
|
+
|
1140
1223
|
class RpcWorker(Listener):
|
1141
1224
|
def __init__(self, queue: str, prefetch_count: int = 250):
|
1142
1225
|
super().__init__(
|
@@ -1166,6 +1249,26 @@ class RpcWorker(Listener):
|
|
1166
1249
|
return wrapper
|
1167
1250
|
|
1168
1251
|
|
1252
|
+
def rpc_worker(queue: str, prefetch_count: int = 250) -> RpcWorker:
|
1253
|
+
return RpcWorker(queue=queue, prefetch_count=prefetch_count)
|
1254
|
+
|
1255
|
+
|
1256
|
+
def execute(procedure: str | None = None) -> Callable[[Callable], Callable]:
|
1257
|
+
def wrapper(worker_method: Callable) -> Callable:
|
1258
|
+
if not callable(worker_method):
|
1259
|
+
raise TypeError(
|
1260
|
+
f"worker method argument not a callable, got {type(worker_method)}"
|
1261
|
+
)
|
1262
|
+
|
1263
|
+
setattr(
|
1264
|
+
worker_method, RPC_WORKER_ATTRIBUTE, ListenerMethodMeta(procedure)
|
1265
|
+
)
|
1266
|
+
|
1267
|
+
return worker_method
|
1268
|
+
|
1269
|
+
return wrapper
|
1270
|
+
|
1271
|
+
|
1169
1272
|
@dataclass
|
1170
1273
|
class ListenerMethodMeta:
|
1171
1274
|
listener_name: str | None = None
|
@@ -1253,39 +1356,3 @@ class ListenerBase:
|
|
1253
1356
|
)
|
1254
1357
|
|
1255
1358
|
return listener_method_attribute, attribute, listener_method_meta
|
1256
|
-
|
1257
|
-
|
1258
|
-
def consume(
|
1259
|
-
target: str | None = None, retry_policy: RetryPolicy | None = None
|
1260
|
-
) -> Callable[[Callable], Callable]:
|
1261
|
-
def wrapper(consumer_method: Callable) -> Callable:
|
1262
|
-
if not callable(consumer_method):
|
1263
|
-
raise TypeError(
|
1264
|
-
f"consumer method argument not a callable, got {type(consumer_method)}"
|
1265
|
-
)
|
1266
|
-
|
1267
|
-
setattr(
|
1268
|
-
consumer_method,
|
1269
|
-
CONSUMER_ATTRIBUTE,
|
1270
|
-
ListenerMethodMeta(listener_name=target, retry_policy=retry_policy),
|
1271
|
-
)
|
1272
|
-
|
1273
|
-
return consumer_method
|
1274
|
-
|
1275
|
-
return wrapper
|
1276
|
-
|
1277
|
-
|
1278
|
-
def execute(procedure: str | None = None) -> Callable[[Callable], Callable]:
|
1279
|
-
def wrapper(worker_method: Callable) -> Callable:
|
1280
|
-
if not callable(worker_method):
|
1281
|
-
raise TypeError(
|
1282
|
-
f"worker method argument not a callable, got {type(worker_method)}"
|
1283
|
-
)
|
1284
|
-
|
1285
|
-
setattr(
|
1286
|
-
worker_method, RPC_WORKER_ATTRIBUTE, ListenerMethodMeta(procedure)
|
1287
|
-
)
|
1288
|
-
|
1289
|
-
return worker_method
|
1290
|
-
|
1291
|
-
return wrapper
|
@@ -4,6 +4,7 @@ from pika import BasicProperties
|
|
4
4
|
from prometheus_client import Counter
|
5
5
|
from pydantic_core import to_json
|
6
6
|
|
7
|
+
from ..exceptions import RabbitMQBlockedError
|
7
8
|
from ..logging import LoggerProvider
|
8
9
|
from ._pool import ChannelPool
|
9
10
|
|
@@ -47,7 +48,7 @@ class Publisher:
|
|
47
48
|
|
48
49
|
async def _get_channel_and_publish(self, message: Any):
|
49
50
|
if self._blocked_connection_check_callback():
|
50
|
-
raise
|
51
|
+
raise RabbitMQBlockedError(
|
51
52
|
"rabbitmq broker is not able to accept message right now"
|
52
53
|
)
|
53
54
|
|
@@ -12,9 +12,14 @@ from pika.spec import Basic
|
|
12
12
|
from prometheus_client import Counter, Summary
|
13
13
|
from pydantic_core import from_json, to_json
|
14
14
|
|
15
|
+
from ..exceptions import (
|
16
|
+
RabbitMQBlockedError,
|
17
|
+
RabbitMQRpcRequestPendingError,
|
18
|
+
RabbitMQRpcRequestTimeoutError,
|
19
|
+
RabbitMQServiceException,
|
20
|
+
)
|
15
21
|
from ..logging import LoggerProvider
|
16
22
|
from ..utils import AsyncEventLoopMixin, TypeAdapterCache
|
17
|
-
from ._exceptions import RabbitMQException
|
18
23
|
from ._pool import ChannelPool
|
19
24
|
|
20
25
|
__all__ = ["RpcClient"]
|
@@ -97,7 +102,7 @@ class RpcClient(AsyncEventLoopMixin):
|
|
97
102
|
procedure: str | None = None,
|
98
103
|
headers: dict[str, str] | None = None,
|
99
104
|
return_type: type | None = None,
|
100
|
-
timeout: float =
|
105
|
+
timeout: float = 15,
|
101
106
|
):
|
102
107
|
self._routing_key = routing_key
|
103
108
|
self._exchange = exchange or ""
|
@@ -130,13 +135,15 @@ class RpcClient(AsyncEventLoopMixin):
|
|
130
135
|
|
131
136
|
async def _get_channel_and_call(self, message: Any) -> Any:
|
132
137
|
if self._blocked_connection_check_callback():
|
133
|
-
raise
|
138
|
+
raise RabbitMQBlockedError(
|
134
139
|
"rabbitmq broker is not able to accept message right now"
|
135
140
|
)
|
136
141
|
|
137
142
|
async with self._rpc_call_lock:
|
138
143
|
if self._rpc_call_pending:
|
139
|
-
raise
|
144
|
+
raise RabbitMQRpcRequestPendingError(
|
145
|
+
"previous rpc request not done yet"
|
146
|
+
)
|
140
147
|
|
141
148
|
self._rpc_call_pending = True
|
142
149
|
|
@@ -200,7 +207,7 @@ class RpcClient(AsyncEventLoopMixin):
|
|
200
207
|
|
201
208
|
def _on_timeout(self):
|
202
209
|
self._finalize_call(
|
203
|
-
exception=
|
210
|
+
exception=RabbitMQRpcRequestTimeoutError(
|
204
211
|
f"rpc worker didn't responed in a timely manner within `{self._timeout}` seconds"
|
205
212
|
)
|
206
213
|
)
|
@@ -232,7 +239,7 @@ class RpcClient(AsyncEventLoopMixin):
|
|
232
239
|
|
233
240
|
if isinstance(response, dict) and "exception" in response:
|
234
241
|
self._finalize_call(
|
235
|
-
exception=
|
242
|
+
exception=RabbitMQServiceException(
|
236
243
|
code=response.get("code") or 0,
|
237
244
|
message=response.get("message")
|
238
245
|
or "unknown error occured from the rpc worker side",
|
qena_shared_lib/scheduler.py
CHANGED
@@ -20,6 +20,7 @@ from .utils import AsyncEventLoopMixin
|
|
20
20
|
__all__ = [
|
21
21
|
"schedule",
|
22
22
|
"ScheduleManager",
|
23
|
+
"scheduler",
|
23
24
|
"Scheduler",
|
24
25
|
"SchedulerBase",
|
25
26
|
]
|
@@ -108,6 +109,10 @@ class Scheduler:
|
|
108
109
|
return self._scheduled_tasks
|
109
110
|
|
110
111
|
|
112
|
+
def scheduler() -> Scheduler:
|
113
|
+
return Scheduler()
|
114
|
+
|
115
|
+
|
111
116
|
@dataclass
|
112
117
|
class ScheduledTaskMeta:
|
113
118
|
cron_expression: str
|
qena_shared_lib/security.py
CHANGED
@@ -15,7 +15,7 @@ from .exceptions import Unauthorized
|
|
15
15
|
from .utils import AsyncEventLoopMixin
|
16
16
|
|
17
17
|
__all__ = [
|
18
|
-
"
|
18
|
+
"Authorization",
|
19
19
|
"get_int_from_datetime",
|
20
20
|
"get_time_from_int",
|
21
21
|
"jwk_from_dict",
|
@@ -137,7 +137,7 @@ class PermissionMatch(Enum):
|
|
137
137
|
ALL = 1
|
138
138
|
|
139
139
|
|
140
|
-
def
|
140
|
+
def Authorization(
|
141
141
|
user_type: str | None = None,
|
142
142
|
permissions: list[str] | None = None,
|
143
143
|
permission_match_strategy: PermissionMatch | None = None,
|
@@ -186,7 +186,7 @@ class EndpointAclValidator:
|
|
186
186
|
tags=[user_info.user_id],
|
187
187
|
extra={
|
188
188
|
"userId": user_info.user_id,
|
189
|
-
"
|
189
|
+
"userType": user_info.user_type,
|
190
190
|
"userPermissions": str(user_info.user_permissions or []),
|
191
191
|
"requiredUserType": self._user_type or "None",
|
192
192
|
"requiredPermissions": str(self._permissions or []),
|