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.
@@ -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
+ ))