DefStream-RabbitMQ 0.1.1__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.
- DefStream/Broker/Implements/RabbitMQ/Configuration.py +48 -0
- DefStream/Broker/Implements/RabbitMQ/Connection.py +449 -0
- DefStream/Broker/Implements/RabbitMQ/Consumer.py +244 -0
- DefStream/Broker/Implements/RabbitMQ/Event.py +106 -0
- DefStream/Broker/Implements/RabbitMQ/Exchange.py +222 -0
- DefStream/Broker/Implements/RabbitMQ/Policies.py +9 -0
- DefStream/Broker/Implements/RabbitMQ/Queue.py +248 -0
- DefStream/Broker/Implements/RabbitMQ/Serializer.py +61 -0
- DefStream/Broker/Implements/RabbitMQ/Stream.py +119 -0
- DefStream/Broker/Implements/RabbitMQ/Subscription.py +47 -0
- DefStream/Broker/Implements/RabbitMQ/__init__.py +47 -0
- defstream_rabbitmq-0.1.1.dist-info/METADATA +143 -0
- defstream_rabbitmq-0.1.1.dist-info/RECORD +16 -0
- defstream_rabbitmq-0.1.1.dist-info/WHEEL +5 -0
- defstream_rabbitmq-0.1.1.dist-info/licenses/LICENSE +674 -0
- defstream_rabbitmq-0.1.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
from ... import Configuration as BaseConfiguration
|
|
4
|
+
|
|
5
|
+
from .Serializer import (
|
|
6
|
+
Encoder,
|
|
7
|
+
Decoder,
|
|
8
|
+
JavaScriptObjectNotationEncoder,
|
|
9
|
+
JavaScriptObjectNotationDecoder,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
from pika import SSLOptions
|
|
14
|
+
from ssl import create_default_context
|
|
15
|
+
|
|
16
|
+
__all__ = (
|
|
17
|
+
'Configuration'
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class Configuration(BaseConfiguration):
|
|
22
|
+
"""
|
|
23
|
+
Configuration of Event Broker for RabbitMQ
|
|
24
|
+
"""
|
|
25
|
+
def __init__(
|
|
26
|
+
self,
|
|
27
|
+
host: str,
|
|
28
|
+
port: int,
|
|
29
|
+
username: str = None,
|
|
30
|
+
password: str = None,
|
|
31
|
+
ssl: bool = False,
|
|
32
|
+
vhost: str = '/',
|
|
33
|
+
timeout: int = None,
|
|
34
|
+
heartbeat: int = None,
|
|
35
|
+
encode: Encoder = JavaScriptObjectNotationEncoder(),
|
|
36
|
+
decode: Decoder = JavaScriptObjectNotationDecoder(),
|
|
37
|
+
):
|
|
38
|
+
self.host = host
|
|
39
|
+
self.port = port
|
|
40
|
+
self.username = username
|
|
41
|
+
self.password = password
|
|
42
|
+
self.ssl = SSLOptions(create_default_context(), host) if ssl else None
|
|
43
|
+
self.vhost = vhost
|
|
44
|
+
self.timeout = timeout/1000 if timeout else None
|
|
45
|
+
self.heartbeat = int(heartbeat*2/1000) if heartbeat else None
|
|
46
|
+
self.encode = encode
|
|
47
|
+
self.decode = decode
|
|
48
|
+
return
|
|
@@ -0,0 +1,449 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
from ...Connection import (
|
|
4
|
+
Connection as BaseConnection,
|
|
5
|
+
GetExchange,
|
|
6
|
+
GetQueue,
|
|
7
|
+
GetStream,
|
|
8
|
+
GetConsumer,
|
|
9
|
+
Declarable,
|
|
10
|
+
TaskSender,
|
|
11
|
+
Requestable,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
from .Configuration import Configuration
|
|
15
|
+
|
|
16
|
+
from .Exchange import Exchange
|
|
17
|
+
from .Queue import Queue
|
|
18
|
+
from .Stream import Stream
|
|
19
|
+
from .Consumer import Consumer
|
|
20
|
+
from .Subscription import Subscription
|
|
21
|
+
from ...Event import EventHandler
|
|
22
|
+
|
|
23
|
+
from pika import (
|
|
24
|
+
BlockingConnection,
|
|
25
|
+
ConnectionParameters,
|
|
26
|
+
PlainCredentials,
|
|
27
|
+
BasicProperties,
|
|
28
|
+
)
|
|
29
|
+
from pika.exceptions import *
|
|
30
|
+
|
|
31
|
+
from collections.abc import Mapping
|
|
32
|
+
from operator import (
|
|
33
|
+
eq,
|
|
34
|
+
ne,
|
|
35
|
+
)
|
|
36
|
+
from enum import Enum
|
|
37
|
+
from uuid import uuid4
|
|
38
|
+
|
|
39
|
+
from typing import (
|
|
40
|
+
Any,
|
|
41
|
+
Iterator,
|
|
42
|
+
Union,
|
|
43
|
+
Optional,
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
__all__ = (
|
|
47
|
+
'Connection',
|
|
48
|
+
'ExchangeType',
|
|
49
|
+
'Parameters',
|
|
50
|
+
'All',
|
|
51
|
+
'Any',
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class ExchangeType(str, Enum):
|
|
56
|
+
Direct = 'direct'
|
|
57
|
+
FanOut = 'fanout'
|
|
58
|
+
Topic = 'topic'
|
|
59
|
+
Header = 'headers'
|
|
60
|
+
def __str__(self): return str(self.value)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class Parameters(Mapping):
|
|
64
|
+
"""Bind Parameters Event Broker for RabbitMQ"""
|
|
65
|
+
def __init__(self, **kwargs):
|
|
66
|
+
self.args = kwargs
|
|
67
|
+
return
|
|
68
|
+
|
|
69
|
+
def __iter__(self) -> Iterator:
|
|
70
|
+
return self.args.__iter__()
|
|
71
|
+
|
|
72
|
+
def __reversed__(self) -> Iterator:
|
|
73
|
+
return self.args.__reversed__()
|
|
74
|
+
|
|
75
|
+
def __len__(self) -> int:
|
|
76
|
+
return self.args.__len__()
|
|
77
|
+
|
|
78
|
+
def __getitem__(self, key: Any) -> Any:
|
|
79
|
+
return self.args.__getitem__(key)
|
|
80
|
+
|
|
81
|
+
def __contains__(self, key: object) -> bool:
|
|
82
|
+
return self.args.__contains__(key)
|
|
83
|
+
|
|
84
|
+
def __eq__(self, other) -> bool:
|
|
85
|
+
return eq(self.args, other)
|
|
86
|
+
|
|
87
|
+
def __ne__(self, other) -> bool:
|
|
88
|
+
return ne(self.args, other)
|
|
89
|
+
|
|
90
|
+
def keys(self) -> Iterator:
|
|
91
|
+
return self.args.keys()
|
|
92
|
+
|
|
93
|
+
def items(self):
|
|
94
|
+
return self.args.items()
|
|
95
|
+
|
|
96
|
+
def values(self):
|
|
97
|
+
return self.args.values()
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class Connection(
|
|
101
|
+
BaseConnection,
|
|
102
|
+
GetExchange,
|
|
103
|
+
GetQueue,
|
|
104
|
+
GetStream,
|
|
105
|
+
GetConsumer,
|
|
106
|
+
Declarable,
|
|
107
|
+
TaskSender,
|
|
108
|
+
Requestable,
|
|
109
|
+
):
|
|
110
|
+
"""Connection of Event Broker for RabbitMQ"""
|
|
111
|
+
def __init__(self, conf: Configuration):
|
|
112
|
+
self.conf = conf
|
|
113
|
+
self.parameters = ConnectionParameters(
|
|
114
|
+
host=self.conf.host,
|
|
115
|
+
port=self.conf.port,
|
|
116
|
+
credentials=PlainCredentials(self.conf.username, self.conf.password),
|
|
117
|
+
virtual_host=self.conf.vhost,
|
|
118
|
+
ssl_options=self.conf.ssl,
|
|
119
|
+
blocked_connection_timeout=self.conf.timeout,
|
|
120
|
+
heartbeat=self.conf.heartbeat
|
|
121
|
+
)
|
|
122
|
+
self.connection = None
|
|
123
|
+
self.publisher = None
|
|
124
|
+
return
|
|
125
|
+
|
|
126
|
+
def __del__(self):
|
|
127
|
+
self.close()
|
|
128
|
+
return
|
|
129
|
+
|
|
130
|
+
def connect(self):
|
|
131
|
+
if self.connection and self.connection.is_open:
|
|
132
|
+
return
|
|
133
|
+
self.connection = BlockingConnection(parameters=self.parameters)
|
|
134
|
+
return
|
|
135
|
+
|
|
136
|
+
def connected(self):
|
|
137
|
+
if self.connection is None or not self.connection.is_open:
|
|
138
|
+
raise RuntimeError('Connect the RabbitMQ connection before use')
|
|
139
|
+
return self.connection
|
|
140
|
+
|
|
141
|
+
def declare(
|
|
142
|
+
self,
|
|
143
|
+
parameters: Any,
|
|
144
|
+
) -> None:
|
|
145
|
+
subscription = Subscription.parse(parameters)
|
|
146
|
+
channel = self.connected().channel()
|
|
147
|
+
try:
|
|
148
|
+
arguments = {}
|
|
149
|
+
if subscription.error is not None:
|
|
150
|
+
channel.queue_declare(
|
|
151
|
+
subscription.error,
|
|
152
|
+
durable=True,
|
|
153
|
+
)
|
|
154
|
+
arguments = {
|
|
155
|
+
'x-dead-letter-exchange': '',
|
|
156
|
+
'x-dead-letter-routing-key': subscription.error,
|
|
157
|
+
}
|
|
158
|
+
channel.queue_declare(
|
|
159
|
+
subscription.queue,
|
|
160
|
+
durable=True,
|
|
161
|
+
arguments=arguments,
|
|
162
|
+
)
|
|
163
|
+
finally:
|
|
164
|
+
if channel.is_open:
|
|
165
|
+
channel.close()
|
|
166
|
+
|
|
167
|
+
def send(
|
|
168
|
+
self,
|
|
169
|
+
body: Any,
|
|
170
|
+
parameters: Any,
|
|
171
|
+
) -> str:
|
|
172
|
+
subscription = Subscription.parse(parameters)
|
|
173
|
+
connection = self.connected()
|
|
174
|
+
channel = self.publisher
|
|
175
|
+
if channel is None or not channel.is_open:
|
|
176
|
+
channel = connection.channel()
|
|
177
|
+
try:
|
|
178
|
+
channel.confirm_delivery()
|
|
179
|
+
except Exception:
|
|
180
|
+
if channel.is_open:
|
|
181
|
+
channel.close()
|
|
182
|
+
raise
|
|
183
|
+
self.publisher = channel
|
|
184
|
+
id = uuid4().hex
|
|
185
|
+
channel.basic_publish(
|
|
186
|
+
exchange='',
|
|
187
|
+
routing_key=subscription.queue,
|
|
188
|
+
body=self.conf.encode(body),
|
|
189
|
+
properties=BasicProperties(
|
|
190
|
+
message_id=id,
|
|
191
|
+
headers={},
|
|
192
|
+
content_type=self.conf.encode.format,
|
|
193
|
+
content_encoding=self.conf.encode.charset,
|
|
194
|
+
delivery_mode=2,
|
|
195
|
+
),
|
|
196
|
+
mandatory=True,
|
|
197
|
+
)
|
|
198
|
+
return id
|
|
199
|
+
|
|
200
|
+
def request(
|
|
201
|
+
self,
|
|
202
|
+
body: Any,
|
|
203
|
+
parameters: Any,
|
|
204
|
+
timeout: float = 5,
|
|
205
|
+
) -> Any:
|
|
206
|
+
subscription = Subscription.parse(parameters)
|
|
207
|
+
self.connected()
|
|
208
|
+
queue = self.queue(subscription.queue)
|
|
209
|
+
try:
|
|
210
|
+
return queue.call(
|
|
211
|
+
body,
|
|
212
|
+
timeout=timeout,
|
|
213
|
+
)
|
|
214
|
+
finally:
|
|
215
|
+
if queue.channel.is_open:
|
|
216
|
+
queue.channel.close()
|
|
217
|
+
|
|
218
|
+
def exchange(self, exchange: str) -> Exchange:
|
|
219
|
+
return Exchange(
|
|
220
|
+
self.connection,
|
|
221
|
+
self.conf.encode,
|
|
222
|
+
exchange,
|
|
223
|
+
decode=self.conf.decode,
|
|
224
|
+
rpc_parameters=self.parameters,
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
def queue(self, queue: str) -> Queue:
|
|
228
|
+
return Queue(
|
|
229
|
+
self.connection,
|
|
230
|
+
self.conf.encode,
|
|
231
|
+
self.conf.decode,
|
|
232
|
+
queue,
|
|
233
|
+
rpc_parameters=self.parameters,
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
def stream(self, queue: str) -> Stream:
|
|
237
|
+
return Stream(self.connection, self.conf.encode, self.conf.decode, queue)
|
|
238
|
+
|
|
239
|
+
def consumer(
|
|
240
|
+
self,
|
|
241
|
+
handler: Optional[EventHandler] = None,
|
|
242
|
+
qos: int = 1,
|
|
243
|
+
offset: int = None,
|
|
244
|
+
) -> Consumer:
|
|
245
|
+
return Consumer(
|
|
246
|
+
self.connection,
|
|
247
|
+
self.conf.decode,
|
|
248
|
+
handler,
|
|
249
|
+
qos=qos,
|
|
250
|
+
offset=offset,
|
|
251
|
+
encode=self.conf.encode,
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
def close(self):
|
|
255
|
+
if self.connection and self.connection.is_open:
|
|
256
|
+
self.connection.close()
|
|
257
|
+
self.connection = None
|
|
258
|
+
self.publisher = None
|
|
259
|
+
return
|
|
260
|
+
|
|
261
|
+
def create_exchange(
|
|
262
|
+
self,
|
|
263
|
+
name: str,
|
|
264
|
+
type: ExchangeType,
|
|
265
|
+
durable: bool = True,
|
|
266
|
+
autodelete: bool = False,
|
|
267
|
+
alter: Union[str, Exchange] = None,
|
|
268
|
+
):
|
|
269
|
+
args = {}
|
|
270
|
+
if alter:
|
|
271
|
+
args['alternate-exchange'] = str(alter)
|
|
272
|
+
channel = self.connection.channel()
|
|
273
|
+
channel.exchange_declare(
|
|
274
|
+
name,
|
|
275
|
+
str(type),
|
|
276
|
+
durable=durable,
|
|
277
|
+
auto_delete=autodelete,
|
|
278
|
+
arguments=args
|
|
279
|
+
)
|
|
280
|
+
return
|
|
281
|
+
|
|
282
|
+
def delete_exchange(
|
|
283
|
+
self,
|
|
284
|
+
name: str,
|
|
285
|
+
):
|
|
286
|
+
channel = self.connection.channel()
|
|
287
|
+
channel.exchange_delete(name)
|
|
288
|
+
return
|
|
289
|
+
|
|
290
|
+
def bind_exchange_to_exchange(
|
|
291
|
+
self,
|
|
292
|
+
src: Union[str, Exchange],
|
|
293
|
+
to: Union[str, Exchange],
|
|
294
|
+
event: str = None,
|
|
295
|
+
parameters: Parameters = None,
|
|
296
|
+
):
|
|
297
|
+
channel = self.connection.channel()
|
|
298
|
+
channel.exchange_bind(
|
|
299
|
+
str(src),
|
|
300
|
+
str(to),
|
|
301
|
+
routing_key=event if event else '',
|
|
302
|
+
arguments=parameters
|
|
303
|
+
)
|
|
304
|
+
return
|
|
305
|
+
|
|
306
|
+
def unbind_exchange_to_exchange(
|
|
307
|
+
self,
|
|
308
|
+
src: Union[str, Exchange],
|
|
309
|
+
to: Union[str, Exchange],
|
|
310
|
+
event: str = None,
|
|
311
|
+
parameters: Parameters = None,
|
|
312
|
+
):
|
|
313
|
+
channel = self.connection.channel()
|
|
314
|
+
channel.exchange_unbind(
|
|
315
|
+
str(src),
|
|
316
|
+
str(to),
|
|
317
|
+
routing_key=event if event else '',
|
|
318
|
+
arguments=parameters,
|
|
319
|
+
)
|
|
320
|
+
return
|
|
321
|
+
|
|
322
|
+
def create_queue(
|
|
323
|
+
self,
|
|
324
|
+
name: str,
|
|
325
|
+
expires: int = None,
|
|
326
|
+
ttl: int = None,
|
|
327
|
+
limit: int = None,
|
|
328
|
+
size: int = None,
|
|
329
|
+
maxPriority: int = None,
|
|
330
|
+
durable: bool = True,
|
|
331
|
+
autodelete: bool = False,
|
|
332
|
+
errorQueue: Union[str, Queue] = None,
|
|
333
|
+
errorExchange: Union[str, Exchange] = None,
|
|
334
|
+
):
|
|
335
|
+
args = {}
|
|
336
|
+
if errorQueue:
|
|
337
|
+
args['x-dead-letter-exchange'] = ''
|
|
338
|
+
args['x-dead-letter-routing-key'] = str(errorQueue)
|
|
339
|
+
if errorExchange:
|
|
340
|
+
args['x-dead-letter-exchange'] = str(errorExchange)
|
|
341
|
+
if expires:
|
|
342
|
+
args['x-expires'] = expires
|
|
343
|
+
if ttl:
|
|
344
|
+
args['x-message-ttl'] = ttl
|
|
345
|
+
if limit:
|
|
346
|
+
args['x-max-length'] = limit
|
|
347
|
+
if size:
|
|
348
|
+
args['x-max-length-bytes'] = size
|
|
349
|
+
if maxPriority:
|
|
350
|
+
args['x-max-priority'] = maxPriority
|
|
351
|
+
channel = self.connection.channel()
|
|
352
|
+
channel.queue_declare(
|
|
353
|
+
name,
|
|
354
|
+
durable=durable,
|
|
355
|
+
auto_delete=autodelete,
|
|
356
|
+
arguments=args
|
|
357
|
+
)
|
|
358
|
+
return
|
|
359
|
+
|
|
360
|
+
def delete_queue(
|
|
361
|
+
self,
|
|
362
|
+
queue: str,
|
|
363
|
+
):
|
|
364
|
+
channel = self.connection.channel()
|
|
365
|
+
channel.queue_delete(queue)
|
|
366
|
+
return
|
|
367
|
+
|
|
368
|
+
def bind_exchange_to_queue(
|
|
369
|
+
self,
|
|
370
|
+
src: Union[str, Exchange],
|
|
371
|
+
to: Union[str, Queue],
|
|
372
|
+
event: str = None,
|
|
373
|
+
parameters: Parameters = None,
|
|
374
|
+
):
|
|
375
|
+
channel = self.connection.channel()
|
|
376
|
+
channel.queue_bind(
|
|
377
|
+
str(to),
|
|
378
|
+
str(src),
|
|
379
|
+
routing_key=event if event else '',
|
|
380
|
+
arguments=parameters
|
|
381
|
+
)
|
|
382
|
+
return
|
|
383
|
+
|
|
384
|
+
def unbind_exchange_to_queue(
|
|
385
|
+
self,
|
|
386
|
+
src: Union[str, Exchange],
|
|
387
|
+
to: Union[str, Queue],
|
|
388
|
+
event: str = None,
|
|
389
|
+
parameters: Parameters = None,
|
|
390
|
+
):
|
|
391
|
+
channel = self.connection.channel()
|
|
392
|
+
channel.queue_unbind(
|
|
393
|
+
str(to),
|
|
394
|
+
str(src),
|
|
395
|
+
routing_key=event if event else '',
|
|
396
|
+
arguments=parameters,
|
|
397
|
+
)
|
|
398
|
+
return
|
|
399
|
+
|
|
400
|
+
def create_stream(
|
|
401
|
+
self,
|
|
402
|
+
name: str,
|
|
403
|
+
size: int = None,
|
|
404
|
+
durable: bool = True,
|
|
405
|
+
autodelete: bool = False,
|
|
406
|
+
):
|
|
407
|
+
channel = self.connection.channel()
|
|
408
|
+
args = {
|
|
409
|
+
'x-queue-type': 'stream',
|
|
410
|
+
}
|
|
411
|
+
if size:
|
|
412
|
+
args['x-max-length-bytes'] = size
|
|
413
|
+
channel.queue_declare(
|
|
414
|
+
name,
|
|
415
|
+
durable=durable,
|
|
416
|
+
auto_delete=autodelete,
|
|
417
|
+
arguments=args
|
|
418
|
+
)
|
|
419
|
+
return
|
|
420
|
+
|
|
421
|
+
def delete_stream(
|
|
422
|
+
self,
|
|
423
|
+
queue: str,
|
|
424
|
+
):
|
|
425
|
+
channel = self.connection.channel()
|
|
426
|
+
channel.queue_delete(queue)
|
|
427
|
+
return
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
class All(Parameters):
|
|
431
|
+
"""All Bind Parameters Event Broker for RabbitMQ"""
|
|
432
|
+
def __init__(self, **kwargs):
|
|
433
|
+
headers = {
|
|
434
|
+
'x-match': 'all-with-x'
|
|
435
|
+
}
|
|
436
|
+
headers.update(kwargs)
|
|
437
|
+
super().__init__(**headers)
|
|
438
|
+
return
|
|
439
|
+
|
|
440
|
+
|
|
441
|
+
class Any(Parameters):
|
|
442
|
+
"""Any Bind Parameters Event Broker for RabbitMQ"""
|
|
443
|
+
def __init__(self, **kwargs):
|
|
444
|
+
headers = {
|
|
445
|
+
'x-match': 'any-with-x'
|
|
446
|
+
}
|
|
447
|
+
headers.update(kwargs)
|
|
448
|
+
super().__init__(**headers)
|
|
449
|
+
return
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
from ... import (
|
|
4
|
+
Consumer as BaseConsumer,
|
|
5
|
+
EventHandler,
|
|
6
|
+
)
|
|
7
|
+
|
|
8
|
+
from .Serializer import (
|
|
9
|
+
Decoder,
|
|
10
|
+
Encoder,
|
|
11
|
+
JavaScriptObjectNotationEncoder,
|
|
12
|
+
)
|
|
13
|
+
from logging import getLogger
|
|
14
|
+
|
|
15
|
+
from .Event import Event
|
|
16
|
+
from .Policies import Requeue
|
|
17
|
+
from .Subscription import Subscription
|
|
18
|
+
from ...Event import (
|
|
19
|
+
Event as BaseEvent,
|
|
20
|
+
EventError,
|
|
21
|
+
)
|
|
22
|
+
from typing import (
|
|
23
|
+
Any,
|
|
24
|
+
Callable,
|
|
25
|
+
Optional,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
from pika import (
|
|
29
|
+
BasicProperties,
|
|
30
|
+
BlockingConnection,
|
|
31
|
+
)
|
|
32
|
+
from pika.exceptions import UnroutableError
|
|
33
|
+
|
|
34
|
+
__all__ = (
|
|
35
|
+
'Consumer'
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
logger = getLogger(__name__)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class Consumer(BaseConsumer):
|
|
42
|
+
"""Consumer of Event Broker for RabbitMQ"""
|
|
43
|
+
def __init__(
|
|
44
|
+
self,
|
|
45
|
+
connection: BlockingConnection,
|
|
46
|
+
decode: Decoder,
|
|
47
|
+
handler: Optional[EventHandler] = None,
|
|
48
|
+
qos: int = 1,
|
|
49
|
+
offset: int = None,
|
|
50
|
+
encode: Optional[Encoder] = None,
|
|
51
|
+
):
|
|
52
|
+
self.connection = connection
|
|
53
|
+
self.decode = decode
|
|
54
|
+
self.channel = self.connection.channel()
|
|
55
|
+
self.qos = qos
|
|
56
|
+
self.channel.basic_qos(
|
|
57
|
+
prefetch_count=qos,
|
|
58
|
+
global_qos=False,
|
|
59
|
+
)
|
|
60
|
+
self.channel.auto_decode = False
|
|
61
|
+
self.offset = offset
|
|
62
|
+
self.handler = handler
|
|
63
|
+
self.encode = encode if encode is not None else JavaScriptObjectNotationEncoder()
|
|
64
|
+
self.response_channel = None
|
|
65
|
+
self.queues = set()
|
|
66
|
+
self.stopped = False
|
|
67
|
+
return
|
|
68
|
+
|
|
69
|
+
def __del__(self):
|
|
70
|
+
self.close()
|
|
71
|
+
return
|
|
72
|
+
|
|
73
|
+
def subs(
|
|
74
|
+
self,
|
|
75
|
+
queue: Any,
|
|
76
|
+
handler: Optional[EventHandler] = None,
|
|
77
|
+
) -> None:
|
|
78
|
+
if handler is None:
|
|
79
|
+
handler = self.handler
|
|
80
|
+
if handler is None:
|
|
81
|
+
raise ValueError('subs requires a handler or a default handler')
|
|
82
|
+
if isinstance(
|
|
83
|
+
queue,
|
|
84
|
+
str,
|
|
85
|
+
):
|
|
86
|
+
subscription = Subscription(
|
|
87
|
+
queue=queue,
|
|
88
|
+
offset=self.offset,
|
|
89
|
+
)
|
|
90
|
+
else:
|
|
91
|
+
subscription = Subscription.parse(queue)
|
|
92
|
+
if subscription.queue in self.queues:
|
|
93
|
+
raise ValueError('A Run is already subscribed to queue {}'.format(subscription.queue))
|
|
94
|
+
self.channel.basic_qos(
|
|
95
|
+
prefetch_count=self.qos if subscription.qos is None else subscription.qos,
|
|
96
|
+
global_qos=False,
|
|
97
|
+
)
|
|
98
|
+
self.channel.basic_consume(
|
|
99
|
+
subscription.queue,
|
|
100
|
+
Callback(
|
|
101
|
+
self.decode,
|
|
102
|
+
handler,
|
|
103
|
+
subscription.queue,
|
|
104
|
+
),
|
|
105
|
+
auto_ack=False,
|
|
106
|
+
exclusive=False,
|
|
107
|
+
arguments={'x-stream-offset': subscription.offset} if subscription.offset is not None else None,
|
|
108
|
+
)
|
|
109
|
+
self.queues.add(subscription.queue)
|
|
110
|
+
|
|
111
|
+
def call(self, callback: Callable[[], None]) -> None:
|
|
112
|
+
self.connection.add_callback_threadsafe(callback)
|
|
113
|
+
|
|
114
|
+
def fail(
|
|
115
|
+
self,
|
|
116
|
+
event: BaseEvent,
|
|
117
|
+
policy: Optional[EventError] = None,
|
|
118
|
+
) -> None:
|
|
119
|
+
if isinstance(
|
|
120
|
+
event,
|
|
121
|
+
Event,
|
|
122
|
+
) and event.properties.reply_to:
|
|
123
|
+
event = FailedRequest(
|
|
124
|
+
self,
|
|
125
|
+
event,
|
|
126
|
+
)
|
|
127
|
+
(policy if policy is not None else Requeue())(event)
|
|
128
|
+
|
|
129
|
+
def complete(
|
|
130
|
+
self,
|
|
131
|
+
event: BaseEvent,
|
|
132
|
+
result: Any,
|
|
133
|
+
) -> None:
|
|
134
|
+
if isinstance(
|
|
135
|
+
event,
|
|
136
|
+
Event,
|
|
137
|
+
) and event.properties.reply_to:
|
|
138
|
+
self.respond(
|
|
139
|
+
event,
|
|
140
|
+
{
|
|
141
|
+
'ok': True,
|
|
142
|
+
'result': result,
|
|
143
|
+
},
|
|
144
|
+
)
|
|
145
|
+
event.ack()
|
|
146
|
+
|
|
147
|
+
def respond(
|
|
148
|
+
self,
|
|
149
|
+
event: Event,
|
|
150
|
+
response: Any,
|
|
151
|
+
) -> None:
|
|
152
|
+
if not event.properties.correlation_id:
|
|
153
|
+
raise ValueError('RPC request requires correlation_id')
|
|
154
|
+
channel = self.response_channel
|
|
155
|
+
if channel is None:
|
|
156
|
+
channel = self.connection.channel()
|
|
157
|
+
channel.confirm_delivery()
|
|
158
|
+
self.response_channel = channel
|
|
159
|
+
try:
|
|
160
|
+
channel.basic_publish(
|
|
161
|
+
exchange='',
|
|
162
|
+
routing_key=event.properties.reply_to,
|
|
163
|
+
body=self.encode(response),
|
|
164
|
+
properties=BasicProperties(
|
|
165
|
+
correlation_id=event.properties.correlation_id,
|
|
166
|
+
content_type=self.encode.format,
|
|
167
|
+
content_encoding=self.encode.charset,
|
|
168
|
+
),
|
|
169
|
+
mandatory=True,
|
|
170
|
+
)
|
|
171
|
+
except UnroutableError:
|
|
172
|
+
logger.warning(
|
|
173
|
+
'RPC reply is unroutable; settling request without a response: correlation_id=%s reply_to=%s',
|
|
174
|
+
event.properties.correlation_id,
|
|
175
|
+
event.properties.reply_to,
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
def close(self) -> None:
|
|
179
|
+
response_channel = getattr(self, 'response_channel', None)
|
|
180
|
+
if response_channel is not None and response_channel.is_open:
|
|
181
|
+
response_channel.close()
|
|
182
|
+
channel = getattr(self, 'channel', None)
|
|
183
|
+
if channel is not None and channel.is_open:
|
|
184
|
+
channel.close()
|
|
185
|
+
|
|
186
|
+
def run(self):
|
|
187
|
+
self.channel.start_consuming()
|
|
188
|
+
return
|
|
189
|
+
|
|
190
|
+
def stop(self):
|
|
191
|
+
if not self.stopped:
|
|
192
|
+
self.stopped = True
|
|
193
|
+
self.channel.stop_consuming()
|
|
194
|
+
return
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
class FailedRequest(BaseEvent):
|
|
198
|
+
def __init__(
|
|
199
|
+
self,
|
|
200
|
+
consumer: Consumer,
|
|
201
|
+
event: Event,
|
|
202
|
+
):
|
|
203
|
+
self.consumer = consumer
|
|
204
|
+
self.event = event
|
|
205
|
+
|
|
206
|
+
@property
|
|
207
|
+
def body(self) -> Any:
|
|
208
|
+
return self.event.body
|
|
209
|
+
|
|
210
|
+
def ack(self) -> None:
|
|
211
|
+
self.event.ack()
|
|
212
|
+
|
|
213
|
+
def nack(self) -> None:
|
|
214
|
+
self.event.nack()
|
|
215
|
+
|
|
216
|
+
def reject(self) -> None:
|
|
217
|
+
self.consumer.respond(
|
|
218
|
+
self.event,
|
|
219
|
+
{
|
|
220
|
+
'ok': False,
|
|
221
|
+
'error': {
|
|
222
|
+
'code': 'ProcessingError',
|
|
223
|
+
'message': 'Request processing failed',
|
|
224
|
+
},
|
|
225
|
+
},
|
|
226
|
+
)
|
|
227
|
+
self.event.reject()
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
class Callback(object):
|
|
231
|
+
def __init__(self, decode: Decoder, handler: EventHandler, queue: str):
|
|
232
|
+
self.decode = decode
|
|
233
|
+
self.handler = handler
|
|
234
|
+
self.queue = queue
|
|
235
|
+
return
|
|
236
|
+
def __call__(self, channel, method, properties, body):
|
|
237
|
+
return self.handler(Event(
|
|
238
|
+
channel,
|
|
239
|
+
self.queue,
|
|
240
|
+
method.consumer_tag,
|
|
241
|
+
method.delivery_tag,
|
|
242
|
+
properties,
|
|
243
|
+
self.decode(body, properties.content_type, properties.content_encoding),
|
|
244
|
+
))
|