qena-shared-lib 0.1.12__py3-none-any.whl → 0.1.13__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 +2 -2
- qena_shared_lib/application.py +71 -29
- qena_shared_lib/background.py +6 -5
- qena_shared_lib/exception_handlers.py +203 -175
- qena_shared_lib/exceptions.py +10 -10
- qena_shared_lib/http.py +16 -16
- qena_shared_lib/rabbitmq/__init__.py +8 -6
- qena_shared_lib/rabbitmq/_base.py +104 -124
- qena_shared_lib/rabbitmq/_channel.py +4 -1
- qena_shared_lib/rabbitmq/_exception_handlers.py +154 -119
- qena_shared_lib/rabbitmq/_listener.py +94 -38
- qena_shared_lib/rabbitmq/_rpc_client.py +4 -4
- qena_shared_lib/remotelogging/__init__.py +15 -0
- qena_shared_lib/{logstash → remotelogging}/_base.py +47 -67
- qena_shared_lib/remotelogging/logstash/__init__.py +9 -0
- qena_shared_lib/remotelogging/logstash/_base.py +32 -0
- qena_shared_lib/{logstash → remotelogging/logstash}/_http_sender.py +5 -4
- qena_shared_lib/{logstash → remotelogging/logstash}/_tcp_sender.py +7 -5
- qena_shared_lib/scheduler.py +49 -24
- qena_shared_lib/security.py +2 -2
- qena_shared_lib/utils.py +9 -3
- {qena_shared_lib-0.1.12.dist-info → qena_shared_lib-0.1.13.dist-info}/METADATA +23 -20
- qena_shared_lib-0.1.13.dist-info/RECORD +31 -0
- qena_shared_lib/logstash/__init__.py +0 -17
- qena_shared_lib-0.1.12.dist-info/RECORD +0 -29
- {qena_shared_lib-0.1.12.dist-info → qena_shared_lib-0.1.13.dist-info}/WHEEL +0 -0
qena_shared_lib/exceptions.py
CHANGED
@@ -73,7 +73,7 @@ class ServiceException(Exception):
|
|
73
73
|
severity: Severity | None = None,
|
74
74
|
tags: list[str] | None = None,
|
75
75
|
extra: dict[str, str] | None = None,
|
76
|
-
|
76
|
+
remote_logging: bool | None = None,
|
77
77
|
extract_exc_info: bool | None = None,
|
78
78
|
):
|
79
79
|
self._message = message
|
@@ -88,10 +88,10 @@ class ServiceException(Exception):
|
|
88
88
|
self._tags = tags
|
89
89
|
self._extra = extra
|
90
90
|
|
91
|
-
if
|
92
|
-
self.
|
91
|
+
if remote_logging is not None:
|
92
|
+
self._remote_logging = remote_logging
|
93
93
|
else:
|
94
|
-
self.
|
94
|
+
self._remote_logging = True
|
95
95
|
|
96
96
|
if extract_exc_info is not None:
|
97
97
|
self._extract_exc_info = extract_exc_info
|
@@ -117,8 +117,8 @@ class ServiceException(Exception):
|
|
117
117
|
return self._extra
|
118
118
|
|
119
119
|
@property
|
120
|
-
def
|
121
|
-
return self.
|
120
|
+
def remote_logging(self) -> bool | None:
|
121
|
+
return self._remote_logging
|
122
122
|
|
123
123
|
@property
|
124
124
|
def extract_exc_info(self) -> bool | None:
|
@@ -145,7 +145,7 @@ class HTTPServiceError(ServiceException):
|
|
145
145
|
severity: Severity | None = None,
|
146
146
|
tags: list[str] | None = None,
|
147
147
|
extra: dict[str, str] | None = None,
|
148
|
-
|
148
|
+
remote_logging: bool = True,
|
149
149
|
extract_exc_info: bool = True,
|
150
150
|
):
|
151
151
|
super().__init__(
|
@@ -153,7 +153,7 @@ class HTTPServiceError(ServiceException):
|
|
153
153
|
severity=severity,
|
154
154
|
tags=tags,
|
155
155
|
extra=extra,
|
156
|
-
|
156
|
+
remote_logging=remote_logging,
|
157
157
|
extract_exc_info=extract_exc_info,
|
158
158
|
)
|
159
159
|
|
@@ -379,7 +379,7 @@ class RabbitMQServiceException(ServiceException):
|
|
379
379
|
severity: Severity | None = None,
|
380
380
|
tags: list[str] | None = None,
|
381
381
|
extra: dict[str, str] | None = None,
|
382
|
-
|
382
|
+
remote_logging: bool | None = None,
|
383
383
|
extract_exc_info: bool | None = None,
|
384
384
|
):
|
385
385
|
super().__init__(
|
@@ -387,7 +387,7 @@ class RabbitMQServiceException(ServiceException):
|
|
387
387
|
severity=severity,
|
388
388
|
tags=tags,
|
389
389
|
extra=extra,
|
390
|
-
|
390
|
+
remote_logging=remote_logging,
|
391
391
|
extract_exc_info=extract_exc_info,
|
392
392
|
)
|
393
393
|
|
qena_shared_lib/http.py
CHANGED
@@ -168,8 +168,8 @@ def get(
|
|
168
168
|
response_class: type[Response] = Default(JSONResponse),
|
169
169
|
name: str | None = None,
|
170
170
|
openapi_extra: dict[str, Any] | None = None,
|
171
|
-
) -> Callable[[Callable], Callable]:
|
172
|
-
def wrapper(route_handler: Callable) -> Callable:
|
171
|
+
) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
|
172
|
+
def wrapper(route_handler: Callable[..., Any]) -> Callable[..., Any]:
|
173
173
|
setattr(
|
174
174
|
route_handler,
|
175
175
|
ROUTE_HANDLER_ATTRIBUTE,
|
@@ -225,8 +225,8 @@ def put(
|
|
225
225
|
response_class: type[Response] = Default(JSONResponse),
|
226
226
|
name: str | None = None,
|
227
227
|
openapi_extra: dict[str, Any] | None = None,
|
228
|
-
) -> Callable[[Callable], Callable]:
|
229
|
-
def wrapper(route_handler: Callable) -> Callable:
|
228
|
+
) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
|
229
|
+
def wrapper(route_handler: Callable[..., Any]) -> Callable[..., Any]:
|
230
230
|
setattr(
|
231
231
|
route_handler,
|
232
232
|
ROUTE_HANDLER_ATTRIBUTE,
|
@@ -282,8 +282,8 @@ def post(
|
|
282
282
|
response_class: type[Response] = Default(JSONResponse),
|
283
283
|
name: str | None = None,
|
284
284
|
openapi_extra: dict[str, Any] | None = None,
|
285
|
-
) -> Callable[[Callable], Callable]:
|
286
|
-
def wrapper(route_handler: Callable) -> Callable:
|
285
|
+
) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
|
286
|
+
def wrapper(route_handler: Callable[..., Any]) -> Callable[..., Any]:
|
287
287
|
setattr(
|
288
288
|
route_handler,
|
289
289
|
ROUTE_HANDLER_ATTRIBUTE,
|
@@ -339,8 +339,8 @@ def delete(
|
|
339
339
|
response_class: type[Response] = Default(JSONResponse),
|
340
340
|
name: str | None = None,
|
341
341
|
openapi_extra: dict[str, Any] | None = None,
|
342
|
-
) -> Callable[[Callable], Callable]:
|
343
|
-
def wrapper(route_handler: Callable) -> Callable:
|
342
|
+
) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
|
343
|
+
def wrapper(route_handler: Callable[..., Any]) -> Callable[..., Any]:
|
344
344
|
setattr(
|
345
345
|
route_handler,
|
346
346
|
ROUTE_HANDLER_ATTRIBUTE,
|
@@ -396,8 +396,8 @@ def options(
|
|
396
396
|
response_class: type[Response] = Default(JSONResponse),
|
397
397
|
name: str | None = None,
|
398
398
|
openapi_extra: dict[str, Any] | None = None,
|
399
|
-
) -> Callable[[Callable], Callable]:
|
400
|
-
def wrapper(route_handler: Callable) -> Callable:
|
399
|
+
) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
|
400
|
+
def wrapper(route_handler: Callable[..., Any]) -> Callable[..., Any]:
|
401
401
|
setattr(
|
402
402
|
route_handler,
|
403
403
|
ROUTE_HANDLER_ATTRIBUTE,
|
@@ -453,8 +453,8 @@ def head(
|
|
453
453
|
response_class: type[Response] = Default(JSONResponse),
|
454
454
|
name: str | None = None,
|
455
455
|
openapi_extra: dict[str, Any] | None = None,
|
456
|
-
) -> Callable[[Callable], Callable]:
|
457
|
-
def wrapper(route_handler: Callable) -> Callable:
|
456
|
+
) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
|
457
|
+
def wrapper(route_handler: Callable[..., Any]) -> Callable[..., Any]:
|
458
458
|
setattr(
|
459
459
|
route_handler,
|
460
460
|
ROUTE_HANDLER_ATTRIBUTE,
|
@@ -510,8 +510,8 @@ def patch(
|
|
510
510
|
response_class: type[Response] = Default(JSONResponse),
|
511
511
|
name: str | None = None,
|
512
512
|
openapi_extra: dict[str, Any] | None = None,
|
513
|
-
) -> Callable[[Callable], Callable]:
|
514
|
-
def wrapper(route_handler: Callable) -> Callable:
|
513
|
+
) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
|
514
|
+
def wrapper(route_handler: Callable[..., Any]) -> Callable[..., Any]:
|
515
515
|
setattr(
|
516
516
|
route_handler,
|
517
517
|
ROUTE_HANDLER_ATTRIBUTE,
|
@@ -567,8 +567,8 @@ def trace(
|
|
567
567
|
response_class: type[Response] = Default(JSONResponse),
|
568
568
|
name: str | None = None,
|
569
569
|
openapi_extra: dict[str, Any] | None = None,
|
570
|
-
) -> Callable[[Callable], Callable]:
|
571
|
-
def wrapper(route_handler: Callable) -> Callable:
|
570
|
+
) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
|
571
|
+
def wrapper(route_handler: Callable[..., Any]) -> Callable[..., Any]:
|
572
572
|
setattr(
|
573
573
|
route_handler,
|
574
574
|
ROUTE_HANDLER_ATTRIBUTE,
|
@@ -1,9 +1,10 @@
|
|
1
1
|
from ._base import AbstractRabbitMQService, RabbitMqManager
|
2
2
|
from ._channel import BaseChannel
|
3
3
|
from ._exception_handlers import (
|
4
|
-
|
5
|
-
|
6
|
-
|
4
|
+
AbstractRabbitMqExceptionHandler,
|
5
|
+
GeneralMqExceptionHandler,
|
6
|
+
RabbitMqServiceExceptionHandler,
|
7
|
+
ValidationErrorHandler,
|
7
8
|
)
|
8
9
|
from ._listener import (
|
9
10
|
CONSUMER_ATTRIBUTE,
|
@@ -27,6 +28,7 @@ from ._publisher import Publisher
|
|
27
28
|
from ._rpc_client import RpcClient
|
28
29
|
|
29
30
|
__all__ = [
|
31
|
+
"AbstractRabbitMqExceptionHandler",
|
30
32
|
"AbstractRabbitMQService",
|
31
33
|
"BackoffRetryDelay",
|
32
34
|
"BaseChannel",
|
@@ -37,18 +39,18 @@ __all__ = [
|
|
37
39
|
"Consumer",
|
38
40
|
"execute",
|
39
41
|
"FixedRetryDelay",
|
40
|
-
"
|
41
|
-
"handle_rabbit_mq_service_exception",
|
42
|
-
"handle_validation_error",
|
42
|
+
"GeneralMqExceptionHandler",
|
43
43
|
"LISTENER_ATTRIBUTE",
|
44
44
|
"ListenerBase",
|
45
45
|
"ListenerContext",
|
46
46
|
"Publisher",
|
47
47
|
"RabbitMqManager",
|
48
|
+
"RabbitMqServiceExceptionHandler",
|
48
49
|
"RetryDelayJitter",
|
49
50
|
"RetryPolicy",
|
50
51
|
"RPC_WORKER_ATTRIBUTE",
|
51
52
|
"rpc_worker",
|
52
53
|
"RpcClient",
|
53
54
|
"RpcWorker",
|
55
|
+
"ValidationErrorHandler",
|
54
56
|
]
|
@@ -5,17 +5,17 @@ from asyncio import (
|
|
5
5
|
gather,
|
6
6
|
iscoroutinefunction,
|
7
7
|
)
|
8
|
-
from dataclasses import dataclass
|
9
8
|
from functools import partial
|
10
|
-
from inspect import Parameter, signature
|
11
9
|
from random import uniform
|
12
10
|
from typing import (
|
13
11
|
Any,
|
14
12
|
Awaitable,
|
15
13
|
Callable,
|
16
14
|
Concatenate,
|
15
|
+
Generic,
|
17
16
|
ParamSpec,
|
18
17
|
TypeVar,
|
18
|
+
cast,
|
19
19
|
)
|
20
20
|
|
21
21
|
from pika.adapters.asyncio_connection import AsyncioConnection
|
@@ -25,20 +25,18 @@ from pika.frame import Method
|
|
25
25
|
from prometheus_client import Counter
|
26
26
|
from prometheus_client import Enum as PrometheusEnum
|
27
27
|
from punq import Container, Scope
|
28
|
-
from pydantic import ValidationError
|
29
28
|
|
30
|
-
from ..dependencies.miscellaneous import validate_annotation
|
31
29
|
from ..exceptions import (
|
32
30
|
RabbitMQConnectionUnhealthyError,
|
33
|
-
ServiceException,
|
34
31
|
)
|
35
32
|
from ..logging import LoggerProvider
|
36
|
-
from ..
|
33
|
+
from ..remotelogging import BaseRemoteLogSender
|
37
34
|
from ..utils import AsyncEventLoopMixin
|
38
35
|
from ._exception_handlers import (
|
39
|
-
|
40
|
-
|
41
|
-
|
36
|
+
AbstractRabbitMqExceptionHandler,
|
37
|
+
GeneralMqExceptionHandler,
|
38
|
+
RabbitMqServiceExceptionHandler,
|
39
|
+
ValidationErrorHandler,
|
42
40
|
)
|
43
41
|
from ._listener import (
|
44
42
|
LISTENER_ATTRIBUTE,
|
@@ -56,29 +54,28 @@ __all__ = [
|
|
56
54
|
"RabbitMqManager",
|
57
55
|
]
|
58
56
|
|
59
|
-
|
57
|
+
|
58
|
+
E = TypeVar("E", bound=BaseException)
|
60
59
|
P = ParamSpec("P")
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
60
|
+
SyncExceptionHandler = Callable[Concatenate[ListenerContext, E, P], None]
|
61
|
+
AsyncExcpetionHandler = Callable[
|
62
|
+
Concatenate[ListenerContext, E, P], Awaitable[None]
|
63
|
+
]
|
64
|
+
ExceptionHandler = SyncExceptionHandler[E, P] | AsyncExcpetionHandler[E, P]
|
65
65
|
R = TypeVar("R")
|
66
66
|
|
67
67
|
|
68
68
|
class AbstractRabbitMQService(ABC):
|
69
69
|
def initialize(
|
70
70
|
self, connection: AsyncioConnection, channel_pool: ChannelPool
|
71
|
-
) -> Future:
|
71
|
+
) -> Future[None]:
|
72
72
|
raise NotImplementedError()
|
73
73
|
|
74
|
-
|
75
|
-
|
76
|
-
class ExceptionHandlerContainer:
|
77
|
-
handler: ExceptionHandler
|
78
|
-
dependencies: dict[str, type]
|
74
|
+
def close(self) -> Future[None]:
|
75
|
+
raise NotImplementedError()
|
79
76
|
|
80
77
|
|
81
|
-
class RabbitMqManager(AsyncEventLoopMixin):
|
78
|
+
class RabbitMqManager(Generic[E, P], AsyncEventLoopMixin):
|
82
79
|
RABBITMQ_CONNECTION_STATE = PrometheusEnum(
|
83
80
|
name="rabbitmq_connection_state",
|
84
81
|
documentation="Babbitmq connection state",
|
@@ -97,7 +94,7 @@ class RabbitMqManager(AsyncEventLoopMixin):
|
|
97
94
|
|
98
95
|
def __init__(
|
99
96
|
self,
|
100
|
-
|
97
|
+
remote_logger: BaseRemoteLogSender,
|
101
98
|
parameters: Parameters | str | None = None,
|
102
99
|
reconnect_delay: float = 5.0,
|
103
100
|
reconnect_delay_jitter: tuple[float, float] = (1.0, 5.0),
|
@@ -121,87 +118,39 @@ class RabbitMqManager(AsyncEventLoopMixin):
|
|
121
118
|
self._connection_blocked = False
|
122
119
|
self._services: list[AbstractRabbitMQService] = []
|
123
120
|
self._exception_handlers: dict[
|
124
|
-
type[Exception],
|
121
|
+
type[Exception], AbstractRabbitMqExceptionHandler
|
125
122
|
] = {}
|
126
|
-
|
127
|
-
self.exception_handler(ServiceException)(
|
128
|
-
handle_rabbit_mq_service_exception
|
129
|
-
)
|
130
|
-
self.exception_handler(ValidationError)(handle_validation_error)
|
131
|
-
self.exception_handler(Exception)(handle_general_mq_exception)
|
132
|
-
|
133
123
|
self._channel_pool = ChannelPool()
|
134
|
-
self.
|
124
|
+
self._remote_logger = remote_logger
|
135
125
|
self._logger = LoggerProvider.default().get_logger("rabbitmq")
|
136
126
|
|
137
127
|
@property
|
138
128
|
def container(self) -> Container:
|
139
129
|
return self._container
|
140
130
|
|
141
|
-
def
|
142
|
-
self,
|
143
|
-
) ->
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
)
|
148
|
-
|
149
|
-
def wrapper(
|
150
|
-
handler: ExceptionHandler,
|
151
|
-
) -> ExceptionHandler:
|
152
|
-
if not callable(handler):
|
153
|
-
raise TypeError(f"handler not a callable, got {type(handler)}")
|
154
|
-
|
155
|
-
dependencies = {}
|
156
|
-
|
157
|
-
for parameter_position, (parameter_name, parameter) in enumerate(
|
158
|
-
signature(handler).parameters.items()
|
131
|
+
def set_exception_handlers(
|
132
|
+
self, *exception_handlers: type[AbstractRabbitMqExceptionHandler]
|
133
|
+
) -> None:
|
134
|
+
for index, exception_handler in enumerate(exception_handlers):
|
135
|
+
if not isinstance(exception_handler, type) or not issubclass(
|
136
|
+
exception_handler, AbstractRabbitMqExceptionHandler
|
159
137
|
):
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
position=parameter_position,
|
164
|
-
annotation=parameter.annotation,
|
165
|
-
)
|
166
|
-
)
|
167
|
-
|
168
|
-
if not is_valid:
|
169
|
-
raise TypeError(
|
170
|
-
f"parameter `{parameter_name}` at `{parameter_position + 1}` is not annotated with type or subclass of `{expected_type}`, got {parameter.annotation}"
|
171
|
-
)
|
172
|
-
|
173
|
-
continue
|
174
|
-
|
175
|
-
dependency = validate_annotation(parameter)
|
176
|
-
|
177
|
-
if dependency is None:
|
178
|
-
raise ValueError(
|
179
|
-
f"handlers cannot contain parameters other than `Annotated[type, DependsOn(type)]`, got `{parameter_name}: {dependency}`"
|
180
|
-
)
|
181
|
-
|
182
|
-
dependencies[parameter_name] = dependency
|
138
|
+
raise TypeError(
|
139
|
+
f"exception handler {index} is {type(exception_handler)}, expected instance of type or subclass of `AbstractRabbitMqExceptionHandler`"
|
140
|
+
)
|
183
141
|
|
184
|
-
self.
|
185
|
-
|
142
|
+
self._container.register(
|
143
|
+
service=AbstractRabbitMqExceptionHandler,
|
144
|
+
factory=exception_handler,
|
145
|
+
scope=Scope.singleton,
|
186
146
|
)
|
187
147
|
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
) -> tuple[bool, type | None]:
|
195
|
-
if annotation is Parameter.empty:
|
196
|
-
return True, None
|
197
|
-
|
198
|
-
if position == 0 and annotation is not ListenerContext:
|
199
|
-
return False, ListenerContext
|
200
|
-
|
201
|
-
if position == 1 and not issubclass(annotation, Exception):
|
202
|
-
return False, Exception
|
203
|
-
|
204
|
-
return True, None
|
148
|
+
def init_default_exception_handlers(self) -> None:
|
149
|
+
self.set_exception_handlers(
|
150
|
+
RabbitMqServiceExceptionHandler,
|
151
|
+
ValidationErrorHandler,
|
152
|
+
GeneralMqExceptionHandler,
|
153
|
+
)
|
205
154
|
|
206
155
|
def include_listener(self, listener: Listener | type[ListenerBase]) -> None:
|
207
156
|
if isinstance(listener, Listener):
|
@@ -259,10 +208,11 @@ class RabbitMqManager(AsyncEventLoopMixin):
|
|
259
208
|
scope=Scope.singleton,
|
260
209
|
)
|
261
210
|
|
262
|
-
def connect(self) -> Future:
|
211
|
+
def connect(self) -> Future[None]:
|
263
212
|
if not self._connected:
|
264
213
|
self._resolve_listener_classes()
|
265
214
|
self._resolve_service_classes()
|
215
|
+
self._resolve_exception_handlers()
|
266
216
|
|
267
217
|
if self._is_connection_healthy():
|
268
218
|
raise RuntimeError("rabbitmq already connected and healthy")
|
@@ -289,6 +239,23 @@ class RabbitMqManager(AsyncEventLoopMixin):
|
|
289
239
|
self._container.resolve_all(AbstractRabbitMQService)
|
290
240
|
)
|
291
241
|
|
242
|
+
def _resolve_exception_handlers(self) -> None:
|
243
|
+
for exception_handler in self._container.resolve_all(
|
244
|
+
AbstractRabbitMqExceptionHandler
|
245
|
+
):
|
246
|
+
exception_handler = cast(
|
247
|
+
AbstractRabbitMqExceptionHandler, exception_handler
|
248
|
+
)
|
249
|
+
|
250
|
+
if not callable(exception_handler):
|
251
|
+
raise ValueError(
|
252
|
+
f"exception handler {exception_handler.__class__.__name__} is not callable"
|
253
|
+
)
|
254
|
+
|
255
|
+
self._exception_handlers[exception_handler.exception] = (
|
256
|
+
exception_handler
|
257
|
+
)
|
258
|
+
|
292
259
|
@property
|
293
260
|
def connection(self) -> AsyncioConnection:
|
294
261
|
if not self._is_connection_healthy():
|
@@ -357,7 +324,7 @@ class RabbitMqManager(AsyncEventLoopMixin):
|
|
357
324
|
and not self._connection.is_closed
|
358
325
|
)
|
359
326
|
|
360
|
-
def disconnect(self) -> None:
|
327
|
+
async def disconnect(self) -> None:
|
361
328
|
if self._disconnected:
|
362
329
|
raise RuntimeError("already disconnected from rabbitmq")
|
363
330
|
|
@@ -366,6 +333,8 @@ class RabbitMqManager(AsyncEventLoopMixin):
|
|
366
333
|
if self._connection is None:
|
367
334
|
raise RabbitMQConnectionUnhealthyError("connection not ready yet")
|
368
335
|
|
336
|
+
await self._wait_for_listeners_and_services()
|
337
|
+
|
369
338
|
if self._connection.is_closing or self._connection.is_closed:
|
370
339
|
self._logger.info("already disconnected from rabbitmq")
|
371
340
|
else:
|
@@ -374,6 +343,16 @@ class RabbitMqManager(AsyncEventLoopMixin):
|
|
374
343
|
|
375
344
|
self.RABBITMQ_CONNECTION_STATE.state("disconnected")
|
376
345
|
|
346
|
+
async def _wait_for_listeners_and_services(self) -> None:
|
347
|
+
_ = await gather(
|
348
|
+
*(listener.cancel() for listener in self._listeners),
|
349
|
+
return_exceptions=True,
|
350
|
+
)
|
351
|
+
_ = await gather(
|
352
|
+
*(service.close() for service in self._services),
|
353
|
+
return_exceptions=True,
|
354
|
+
)
|
355
|
+
|
377
356
|
def _on_connection_opened(self, connection: AsyncioConnection) -> None:
|
378
357
|
self._connection = connection
|
379
358
|
self._connection_blocked = False
|
@@ -396,7 +375,7 @@ class RabbitMqManager(AsyncEventLoopMixin):
|
|
396
375
|
self._channel_pool.fill(self._connection)
|
397
376
|
).add_done_callback(self._channel_pool_filled)
|
398
377
|
|
399
|
-
def _channel_pool_drained(self, task: Task) -> None:
|
378
|
+
def _channel_pool_drained(self, task: Task[None]) -> None:
|
400
379
|
if task.cancelled():
|
401
380
|
if not self._connected and not self._connected_future.done():
|
402
381
|
_ = self._connected_future.cancel(None)
|
@@ -407,7 +386,7 @@ class RabbitMqManager(AsyncEventLoopMixin):
|
|
407
386
|
|
408
387
|
if exception is not None:
|
409
388
|
if self._can_reconnect(exception):
|
410
|
-
self.
|
389
|
+
self._remote_logger.error(
|
411
390
|
message="couldn't drain the channel pool",
|
412
391
|
exception=exception,
|
413
392
|
)
|
@@ -429,7 +408,7 @@ class RabbitMqManager(AsyncEventLoopMixin):
|
|
429
408
|
|
430
409
|
self._connection_blocked = True
|
431
410
|
|
432
|
-
self.
|
411
|
+
self._remote_logger.warning(
|
433
412
|
"connection is blocked by broker, will not accept published messages"
|
434
413
|
)
|
435
414
|
self.RABBITMQ_PUBLISHER_BLOCKED_STATE.state("blocked")
|
@@ -441,13 +420,13 @@ class RabbitMqManager(AsyncEventLoopMixin):
|
|
441
420
|
|
442
421
|
self._connection_blocked = False
|
443
422
|
|
444
|
-
self.
|
423
|
+
self._remote_logger.info("broker resumed accepting published messages")
|
445
424
|
self.RABBITMQ_PUBLISHER_BLOCKED_STATE.state("unblocked")
|
446
425
|
|
447
426
|
def _is_connection_blocked(self) -> bool:
|
448
427
|
return self._connection_blocked
|
449
428
|
|
450
|
-
def _channel_pool_filled(self, task: Task) -> None:
|
429
|
+
def _channel_pool_filled(self, task: Task[None]) -> None:
|
451
430
|
if task.cancelled():
|
452
431
|
if not self._connected and not self._connected_future.done():
|
453
432
|
_ = self._connected_future.cancel(None)
|
@@ -460,7 +439,7 @@ class RabbitMqManager(AsyncEventLoopMixin):
|
|
460
439
|
if not self._connected and not self._connected_future.done():
|
461
440
|
self._connected_future.set_exception(exception)
|
462
441
|
elif self._can_reconnect(exception):
|
463
|
-
self.
|
442
|
+
self._remote_logger.error(
|
464
443
|
message="couldn't fill the channel pool",
|
465
444
|
exception=exception,
|
466
445
|
)
|
@@ -482,7 +461,7 @@ class RabbitMqManager(AsyncEventLoopMixin):
|
|
482
461
|
*self._configure_listeners(), *self._initialize_services()
|
483
462
|
).add_done_callback(self._listener_and_service_config_and_init_done)
|
484
463
|
|
485
|
-
def _configure_listeners(self) -> list[Awaitable]:
|
464
|
+
def _configure_listeners(self) -> list[Awaitable[Any]]:
|
486
465
|
try:
|
487
466
|
assert self._connection is not None
|
488
467
|
|
@@ -492,7 +471,7 @@ class RabbitMqManager(AsyncEventLoopMixin):
|
|
492
471
|
channel_pool=self._channel_pool,
|
493
472
|
on_exception_callback=self._invoke_exception_handler,
|
494
473
|
container=self._container,
|
495
|
-
|
474
|
+
remote_logger=self._remote_logger,
|
496
475
|
global_retry_policy=self._listener_global_retry_policy,
|
497
476
|
)
|
498
477
|
for listener in self._listeners
|
@@ -504,7 +483,7 @@ class RabbitMqManager(AsyncEventLoopMixin):
|
|
504
483
|
|
505
484
|
return [listener_configuration_error_future]
|
506
485
|
|
507
|
-
def _initialize_services(self) -> list[Future]:
|
486
|
+
def _initialize_services(self) -> list[Future[Any]]:
|
508
487
|
assert self._connection is not None
|
509
488
|
|
510
489
|
try:
|
@@ -520,7 +499,7 @@ class RabbitMqManager(AsyncEventLoopMixin):
|
|
520
499
|
return [service_initialization_error_future]
|
521
500
|
|
522
501
|
def _listener_and_service_config_and_init_done(
|
523
|
-
self, future: Future
|
502
|
+
self, future: Future[list[Any]]
|
524
503
|
) -> None:
|
525
504
|
if future.cancelled():
|
526
505
|
if not self._connected and not self._connected_future.done():
|
@@ -534,7 +513,7 @@ class RabbitMqManager(AsyncEventLoopMixin):
|
|
534
513
|
if not self._connected and not self._connected_future.done():
|
535
514
|
self._connected_future.set_exception(exception)
|
536
515
|
elif self._can_reconnect(exception):
|
537
|
-
self.
|
516
|
+
self._remote_logger.error(
|
538
517
|
message="couldn't configure and initialize all listeners and services",
|
539
518
|
exception=exception,
|
540
519
|
)
|
@@ -579,7 +558,7 @@ class RabbitMqManager(AsyncEventLoopMixin):
|
|
579
558
|
return
|
580
559
|
|
581
560
|
if self._connected and not self._disconnected:
|
582
|
-
self.
|
561
|
+
self._remote_logger.error(
|
583
562
|
message="error while opening connection to rabbitmq",
|
584
563
|
exception=exception,
|
585
564
|
)
|
@@ -599,29 +578,18 @@ class RabbitMqManager(AsyncEventLoopMixin):
|
|
599
578
|
if exception_handler is None:
|
600
579
|
return False
|
601
580
|
|
602
|
-
|
603
|
-
|
604
|
-
for (
|
605
|
-
parameter_name,
|
606
|
-
dependency,
|
607
|
-
) in exception_handler.dependencies.items():
|
608
|
-
dependencies[parameter_name] = self._container.resolve(dependency)
|
581
|
+
assert callable(exception_handler)
|
609
582
|
|
610
|
-
if
|
583
|
+
if self._is_async_exception_handler(exception_handler):
|
611
584
|
self.loop.create_task(
|
612
|
-
exception_handler
|
585
|
+
exception_handler(context, exception)
|
613
586
|
).add_done_callback(
|
614
587
|
partial(self._on_exception_handler_done, context)
|
615
588
|
)
|
616
589
|
else:
|
617
590
|
self.loop.run_in_executor(
|
618
591
|
executor=None,
|
619
|
-
func=partial(
|
620
|
-
exception_handler.handler,
|
621
|
-
context,
|
622
|
-
exception,
|
623
|
-
**dependencies,
|
624
|
-
),
|
592
|
+
func=partial(exception_handler, context, exception),
|
625
593
|
).add_done_callback(
|
626
594
|
partial(self._on_exception_handler_done, context)
|
627
595
|
)
|
@@ -634,6 +602,18 @@ class RabbitMqManager(AsyncEventLoopMixin):
|
|
634
602
|
|
635
603
|
return True
|
636
604
|
|
605
|
+
def _is_async_exception_handler(
|
606
|
+
self, exception_handler: AbstractRabbitMqExceptionHandler
|
607
|
+
) -> bool:
|
608
|
+
exception_handler_callable = getattr(
|
609
|
+
exception_handler, "__call__", None
|
610
|
+
)
|
611
|
+
|
612
|
+
if exception_handler_callable is None:
|
613
|
+
raise RuntimeError("exception handler has not `__call__` method")
|
614
|
+
|
615
|
+
return iscoroutinefunction(exception_handler_callable)
|
616
|
+
|
637
617
|
def _on_exception_handler_done(
|
638
618
|
self, context: ListenerContext, task_or_future: Task[Any] | Future[Any]
|
639
619
|
) -> None:
|
@@ -643,7 +623,7 @@ class RabbitMqManager(AsyncEventLoopMixin):
|
|
643
623
|
exception = task_or_future.exception()
|
644
624
|
|
645
625
|
if exception is not None:
|
646
|
-
self.
|
626
|
+
self._remote_logger.error(
|
647
627
|
message="error occured in listener exception handler",
|
648
628
|
exception=exception,
|
649
629
|
)
|
@@ -661,7 +641,7 @@ class RabbitMqManager(AsyncEventLoopMixin):
|
|
661
641
|
return
|
662
642
|
|
663
643
|
if self._can_reconnect(exception):
|
664
|
-
self.
|
644
|
+
self._remote_logger.error(
|
665
645
|
message="connection to rabbitmq closed unexpectedly, attempting to reconnect",
|
666
646
|
exception=exception,
|
667
647
|
)
|
@@ -683,7 +663,7 @@ class RabbitMqManager(AsyncEventLoopMixin):
|
|
683
663
|
if not self._connected_future.done():
|
684
664
|
self._connected_future.set_result(None)
|
685
665
|
|
686
|
-
self.
|
666
|
+
self._remote_logger.exception(
|
687
667
|
"couldn't reconnect to rabbitmq, attempting to reconnect"
|
688
668
|
)
|
689
669
|
self._reconnect()
|
@@ -705,7 +685,7 @@ class RabbitMqManager(AsyncEventLoopMixin):
|
|
705
685
|
return
|
706
686
|
|
707
687
|
if self._can_reconnect(exception):
|
708
|
-
self.
|
688
|
+
self._remote_logger.error(
|
709
689
|
message="couldn't reconnect to rabbitmq, attempting to reconnect",
|
710
690
|
exception=exception,
|
711
691
|
)
|
@@ -1,6 +1,7 @@
|
|
1
1
|
from asyncio import Future
|
2
2
|
from inspect import Traceback
|
3
3
|
from random import uniform
|
4
|
+
from typing import cast
|
4
5
|
from uuid import UUID, uuid4
|
5
6
|
|
6
7
|
from pika.adapters.asyncio_connection import AsyncioConnection
|
@@ -86,7 +87,9 @@ class BaseChannel(AsyncEventLoopMixin):
|
|
86
87
|
if self._channel is None:
|
87
88
|
raise RuntimeError("underlying channel not set")
|
88
89
|
|
89
|
-
return self._channel.is_closing or
|
90
|
+
return cast(bool, self._channel.is_closing) or cast(
|
91
|
+
bool, self._channel.is_closed
|
92
|
+
)
|
90
93
|
|
91
94
|
def _on_cancelled(self, method: Basic.Cancel) -> None:
|
92
95
|
del method
|