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,106 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ from ...Event import (
4
+ Event as BaseEvent,
5
+ EventHandler as BaseEventHandler,
6
+ )
7
+
8
+ from abc import abstractmethod
9
+
10
+ __all__ = (
11
+ 'Event',
12
+ 'EventHandler',
13
+ )
14
+
15
+
16
+ class Event(BaseEvent):
17
+ """Event of Event Broker for RabbitMQ"""
18
+ def __init__(
19
+ self,
20
+ channel,
21
+ queue,
22
+ consumer,
23
+ transaction,
24
+ properties,
25
+ body,
26
+ ):
27
+ self.channel = channel
28
+ self.queue = queue
29
+ self.consumer = consumer
30
+ self.transaction = transaction
31
+ self.properties = properties
32
+ self.props = properties.headers
33
+ self.payload = body
34
+ return
35
+
36
+ def __repr__(self):
37
+ return 'Event(id={}, body={}, type={}, headers={})'.format(
38
+ self.properties.message_id,
39
+ self.payload,
40
+ self.properties.type,
41
+ self.props,
42
+ )
43
+
44
+ def ack(self):
45
+ self.channel.basic_ack(self.transaction)
46
+ return
47
+
48
+ def nack(self):
49
+ self.channel.basic_nack(
50
+ self.transaction,
51
+ requeue=True,
52
+ )
53
+ return
54
+
55
+ def reject(self):
56
+ self.channel.basic_reject(
57
+ self.transaction,
58
+ requeue=False,
59
+ )
60
+ return
61
+
62
+ def headers(self):
63
+ return self.props
64
+
65
+ def header(
66
+ self,
67
+ key,
68
+ ):
69
+ if key not in self.props:
70
+ return None
71
+ return self.props[key]
72
+
73
+ @property
74
+ def src(self):
75
+ return self.queue
76
+
77
+ @property
78
+ def id(self):
79
+ return self.properties.message_id
80
+
81
+ @property
82
+ def type(self):
83
+ return self.properties.type
84
+
85
+ @property
86
+ def body(self):
87
+ return self.payload
88
+
89
+ @property
90
+ def priority(self):
91
+ if not hasattr(
92
+ self.properties,
93
+ 'priority',
94
+ ):
95
+ return None
96
+ return self.properties.priority
97
+
98
+
99
+ class EventHandler(BaseEventHandler):
100
+ """Event Handler Interface of Event Broker for RabbitMQ"""
101
+ @abstractmethod
102
+ def __call__(
103
+ self,
104
+ event: Event,
105
+ ):
106
+ raise NotImplementedError('{} must be implemented __call__'.format(self.__class__.__name__))
@@ -0,0 +1,222 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ from ... import (
4
+ Sendable,
5
+ Callable,
6
+ )
7
+
8
+ from .Serializer import (
9
+ Encoder,
10
+ Decoder,
11
+ )
12
+ from ...Errors import RemoteError
13
+ from copy import deepcopy
14
+ from math import (
15
+ ceil,
16
+ isfinite,
17
+ )
18
+
19
+ from pika import (
20
+ BlockingConnection,
21
+ BasicProperties,
22
+ ConnectionParameters,
23
+ )
24
+
25
+ from time import (
26
+ time,
27
+ monotonic,
28
+ )
29
+ from enum import Enum
30
+ from uuid import uuid4
31
+
32
+ from typing import (
33
+ Any,
34
+ Dict,
35
+ Optional,
36
+ )
37
+
38
+ __all__ = (
39
+ 'Exchange',
40
+ )
41
+
42
+
43
+ class Exchange(
44
+ Sendable,
45
+ Callable,
46
+ ):
47
+ """Exchange of Event Broker for RabbitMQ"""
48
+ def __init__(
49
+ self,
50
+ connection: BlockingConnection,
51
+ encode: Encoder,
52
+ name: str = None,
53
+ decode: Optional[Decoder] = None,
54
+ rpc_parameters: Optional[ConnectionParameters] = None,
55
+ ):
56
+ self.connection = connection
57
+ self.encode = encode
58
+ self.channel = self.connection.channel()
59
+ self.channel.auto_decode = False
60
+ self.exchange = name
61
+ self.decode = decode
62
+ self.rpc_parameters = rpc_parameters
63
+ return
64
+
65
+ def __del__(self):
66
+ if self.channel and self.channel.is_open:
67
+ self.channel.close()
68
+ return
69
+
70
+ def __str__(self): return self.exchange
71
+
72
+ def send(
73
+ self,
74
+ body,
75
+ event: str = None,
76
+ headers: Dict = {},
77
+ priority: int = None,
78
+ expiration: int = None,
79
+ timestamp: int = None,
80
+ persistent: bool = True,
81
+ id: str = None,
82
+ ):
83
+ if not timestamp: timestamp = int(time())
84
+ if not id: id = uuid4().hex
85
+ properties = BasicProperties(
86
+ type=event if event else '',
87
+ headers=headers,
88
+ content_type=self.encode.format,
89
+ content_encoding=self.encode.charset,
90
+ priority=priority,
91
+ timestamp=timestamp,
92
+ expiration=expiration,
93
+ message_id=id,
94
+ delivery_mode=2 if persistent else 1,
95
+ )
96
+ self.channel.basic_publish(
97
+ exchange=self.exchange,
98
+ routing_key=event if event else '',
99
+ properties=properties,
100
+ body=self.encode(body),
101
+ )
102
+ return id
103
+
104
+ def call(
105
+ self,
106
+ body: Any,
107
+ timeout: float = 5,
108
+ *,
109
+ event: Optional[str] = None,
110
+ ) -> Any:
111
+ if self.rpc_parameters is None or self.decode is None:
112
+ raise RuntimeError('RPC requires decoder and connection parameters; obtain the exchange through Connection.exchange')
113
+ if isinstance(
114
+ timeout,
115
+ bool,
116
+ ) or not isinstance(
117
+ timeout,
118
+ (
119
+ int,
120
+ float,
121
+ ),
122
+ ) or not isfinite(timeout) or timeout <= 0:
123
+ raise ValueError('RPC timeout must be a finite positive number of seconds')
124
+ deadline = monotonic() + timeout
125
+ settings = deepcopy(self.rpc_parameters)
126
+ settings.connection_attempts = 1
127
+ settings.socket_timeout = min(
128
+ settings.socket_timeout,
129
+ timeout,
130
+ )
131
+ settings.stack_timeout = timeout
132
+ settings.blocked_connection_timeout = timeout
133
+ connection = BlockingConnection(settings)
134
+ try:
135
+ channel = connection.channel()
136
+ reply_queue = channel.queue_declare(
137
+ queue='',
138
+ exclusive=True,
139
+ auto_delete=True,
140
+ ).method.queue
141
+ channel.confirm_delivery()
142
+ correlation_id = uuid4().hex
143
+ responses = []
144
+
145
+ def receive(
146
+ channel,
147
+ method,
148
+ properties,
149
+ payload,
150
+ ):
151
+ if properties.correlation_id == correlation_id:
152
+ responses.append((
153
+ properties,
154
+ payload,
155
+ ))
156
+
157
+ channel.basic_consume(
158
+ reply_queue,
159
+ on_message_callback=receive,
160
+ auto_ack=True,
161
+ )
162
+ remaining = deadline - monotonic()
163
+ if remaining <= 0:
164
+ raise TimeoutError('RPC timed out before request publication')
165
+ channel.basic_publish(
166
+ exchange=self.exchange,
167
+ routing_key=event if event is not None else '',
168
+ body=self.encode(body),
169
+ properties=BasicProperties(
170
+ reply_to=reply_queue,
171
+ correlation_id=correlation_id,
172
+ content_type=self.encode.format,
173
+ content_encoding=self.encode.charset,
174
+ expiration=str(max(
175
+ 1,
176
+ ceil(remaining * 1000),
177
+ )),
178
+ ),
179
+ mandatory=True,
180
+ )
181
+ while not responses:
182
+ remaining = deadline - monotonic()
183
+ if remaining <= 0:
184
+ raise TimeoutError(f'RPC response timed out for exchange {self.exchange!r} routing_key={event!r}')
185
+ connection.process_data_events(time_limit=min(
186
+ remaining,
187
+ 0.1,
188
+ ))
189
+ properties, payload = responses[0]
190
+ response = self.decode(
191
+ payload,
192
+ properties.content_type,
193
+ properties.content_encoding,
194
+ )
195
+ if not isinstance(
196
+ response,
197
+ dict,
198
+ ) or type(response.get('ok')) is not bool:
199
+ raise ValueError('Invalid RPC response envelope')
200
+ if response['ok']:
201
+ if 'result' not in response:
202
+ raise ValueError('RPC response has no result')
203
+ return response['result']
204
+ error = response.get('error')
205
+ if not isinstance(
206
+ error,
207
+ dict,
208
+ ) or not isinstance(
209
+ error.get('code'),
210
+ str,
211
+ ) or not isinstance(
212
+ error.get('message'),
213
+ str,
214
+ ):
215
+ raise ValueError('Invalid RPC error response')
216
+ raise RemoteError(
217
+ error['code'],
218
+ error['message'],
219
+ )
220
+ finally:
221
+ if connection.is_open:
222
+ connection.close()
@@ -0,0 +1,9 @@
1
+ from ...Policies import (
2
+ Requeue,
3
+ Reject,
4
+ )
5
+
6
+ __all__ = (
7
+ 'Requeue',
8
+ 'Reject',
9
+ )
@@ -0,0 +1,248 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ from ... import (
4
+ Sendable,
5
+ Callable,
6
+ Poppable,
7
+ )
8
+
9
+ from .Serializer import (
10
+ Encoder,
11
+ Decoder,
12
+ )
13
+ from .Event import Event
14
+ from ...Errors import RemoteError
15
+ from copy import deepcopy
16
+ from math import (
17
+ ceil,
18
+ isfinite,
19
+ )
20
+
21
+ from pika import (
22
+ BlockingConnection,
23
+ BasicProperties,
24
+ ConnectionParameters,
25
+ )
26
+
27
+ from time import (
28
+ time,
29
+ sleep,
30
+ monotonic,
31
+ )
32
+ from uuid import uuid4
33
+
34
+ from typing import (
35
+ Any,
36
+ Optional,
37
+ Dict,
38
+ )
39
+
40
+ __all__ = (
41
+ 'Queue'
42
+ )
43
+
44
+
45
+ class Queue(
46
+ Sendable,
47
+ Callable,
48
+ Poppable,
49
+ ):
50
+ """Queue of Event Broker for RabbitMQ"""
51
+ def __init__(
52
+ self,
53
+ connection: BlockingConnection,
54
+ encode: Encoder,
55
+ decode: Decoder,
56
+ name: str = None,
57
+ rpc_parameters: Optional[ConnectionParameters] = None,
58
+ ):
59
+ self.connection = connection
60
+ self.encode = encode
61
+ self.decode = decode
62
+ self.channel = self.connection.channel()
63
+ self.channel.auto_decode = False
64
+ self.queue = name
65
+ self.event = None
66
+ self.rpc_parameters = rpc_parameters
67
+ return
68
+
69
+ def __del__(self):
70
+ if self.channel and self.channel.is_open:
71
+ self.channel.close()
72
+ return
73
+
74
+ def __str__(self): return self.queue
75
+
76
+ def send(
77
+ self,
78
+ body,
79
+ event: str = None,
80
+ headers: Dict = {},
81
+ priority: int = None,
82
+ expiration: int = None,
83
+ timestamp: int = None,
84
+ persistent: bool = True,
85
+ id: str = None,
86
+ reply_to: str = None,
87
+ correlation_id: str = None,
88
+ ):
89
+ if not timestamp: timestamp = int(time())
90
+ if not id: id = uuid4().hex
91
+ properties = BasicProperties(
92
+ type=event if event else '',
93
+ headers=headers,
94
+ content_type=self.encode.format,
95
+ content_encoding=self.encode.charset,
96
+ priority=priority,
97
+ timestamp=timestamp,
98
+ expiration=expiration,
99
+ message_id=id,
100
+ delivery_mode=2 if persistent else 1,
101
+ reply_to=reply_to,
102
+ correlation_id=correlation_id,
103
+ )
104
+ self.channel.basic_publish(
105
+ exchange='',
106
+ routing_key=self.queue,
107
+ properties=properties,
108
+ body=self.encode(body)
109
+ )
110
+ return id
111
+
112
+ def call(
113
+ self,
114
+ body: Any,
115
+ timeout: float = 5,
116
+ ) -> Any:
117
+ if self.rpc_parameters is None:
118
+ raise RuntimeError('RPC requires connection parameters; obtain the queue through Connection.queue')
119
+ if isinstance(
120
+ timeout,
121
+ bool,
122
+ ) or not isinstance(
123
+ timeout,
124
+ (
125
+ int,
126
+ float,
127
+ ),
128
+ ) or not isfinite(timeout) or timeout <= 0:
129
+ raise ValueError('RPC timeout must be a finite positive number of seconds')
130
+ deadline = monotonic() + timeout
131
+ settings = deepcopy(self.rpc_parameters)
132
+ settings.connection_attempts = 1
133
+ settings.socket_timeout = min(
134
+ settings.socket_timeout,
135
+ timeout,
136
+ )
137
+ settings.stack_timeout = timeout
138
+ settings.blocked_connection_timeout = timeout
139
+ connection = BlockingConnection(settings)
140
+ try:
141
+ channel = connection.channel()
142
+ reply_queue = channel.queue_declare(
143
+ queue='',
144
+ exclusive=True,
145
+ auto_delete=True,
146
+ ).method.queue
147
+ channel.confirm_delivery()
148
+ correlation_id = uuid4().hex
149
+ responses = []
150
+
151
+ def receive(
152
+ channel,
153
+ method,
154
+ properties,
155
+ payload,
156
+ ):
157
+ if properties.correlation_id == correlation_id:
158
+ responses.append((
159
+ properties,
160
+ payload,
161
+ ))
162
+
163
+ channel.basic_consume(
164
+ reply_queue,
165
+ on_message_callback=receive,
166
+ auto_ack=True,
167
+ )
168
+ remaining = deadline - monotonic()
169
+ if remaining <= 0:
170
+ raise TimeoutError('RPC timed out before request publication')
171
+ channel.basic_publish(
172
+ exchange='',
173
+ routing_key=self.queue,
174
+ body=self.encode(body),
175
+ properties=BasicProperties(
176
+ reply_to=reply_queue,
177
+ correlation_id=correlation_id,
178
+ content_type=self.encode.format,
179
+ content_encoding=self.encode.charset,
180
+ expiration=str(max(
181
+ 1,
182
+ ceil(remaining * 1000),
183
+ )),
184
+ ),
185
+ mandatory=True,
186
+ )
187
+ while not responses:
188
+ remaining = deadline - monotonic()
189
+ if remaining <= 0:
190
+ raise TimeoutError(f'RPC response timed out for queue {self.queue!r}')
191
+ connection.process_data_events(time_limit=min(
192
+ remaining,
193
+ 0.1,
194
+ ))
195
+ properties, payload = responses[0]
196
+ response = self.decode(
197
+ payload,
198
+ properties.content_type,
199
+ properties.content_encoding,
200
+ )
201
+ if not isinstance(
202
+ response,
203
+ dict,
204
+ ) or type(response.get('ok')) is not bool:
205
+ raise ValueError('Invalid RPC response envelope')
206
+ if response['ok']:
207
+ if 'result' not in response:
208
+ raise ValueError('RPC response has no result')
209
+ return response['result']
210
+ error = response.get('error')
211
+ if not isinstance(
212
+ error,
213
+ dict,
214
+ ) or not isinstance(
215
+ error.get('code'),
216
+ str,
217
+ ) or not isinstance(
218
+ error.get('message'),
219
+ str,
220
+ ):
221
+ raise ValueError('Invalid RPC error response')
222
+ raise RemoteError(
223
+ error['code'],
224
+ error['message'],
225
+ )
226
+ finally:
227
+ if connection.is_open:
228
+ connection.close()
229
+
230
+ def pop(self, timeout: int = None) -> Optional[Event]:
231
+ channel = self.channel
232
+ channel.basic_qos(0, 1, False)
233
+ deadline = time() + (timeout / 1000) if timeout else None
234
+ while True:
235
+ method, properties, body = channel.basic_get(queue=self.queue, auto_ack=False)
236
+ if method:
237
+ return Event(
238
+ channel,
239
+ self.queue,
240
+ None,
241
+ method.delivery_tag,
242
+ properties,
243
+ self.decode(body, properties.content_type, properties.content_encoding),
244
+ )
245
+ if deadline and time() >= deadline:
246
+ break
247
+ sleep(0.1)
248
+ return
@@ -0,0 +1,61 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ from abc import (
4
+ ABCMeta,
5
+ abstractmethod,
6
+ )
7
+
8
+ from json import (
9
+ dumps,
10
+ loads,
11
+ )
12
+ from typing import Any
13
+
14
+ __all__ = (
15
+ 'Encoder',
16
+ 'Decoder',
17
+ 'JavaScriptObjectNotationEncoder',
18
+ 'JavaScriptObjectNotationDecoder',
19
+ )
20
+
21
+ class Encoder(metaclass=ABCMeta):
22
+ """Encoder Interface"""
23
+ @abstractmethod
24
+ def __call__(self, obj: Any) -> bytes:
25
+ raise NotImplementedError('{} must be implemented __call__'.format(self.__class__.__name__))
26
+
27
+ @property
28
+ @abstractmethod
29
+ def format(self) -> str:
30
+ raise NotImplementedError('{} must be implemented format'.format(self.__class__.__name__))
31
+
32
+ @property
33
+ @abstractmethod
34
+ def charset(self) -> str:
35
+ raise NotImplementedError('{} must be implemented charset'.format(self.__class__.__name__))
36
+
37
+
38
+ class Decoder(metaclass=ABCMeta):
39
+ """Decoder Interface"""
40
+ @abstractmethod
41
+ def __call__(self, obj: bytes, format: str, charset: str) -> Any:
42
+ raise NotImplementedError('{} must be implemented __call__'.format(self.__class__.__name__))
43
+
44
+
45
+ class JavaScriptObjectNotationEncoder(Encoder):
46
+ """JavaScriptObjectNotation Encoder"""
47
+ def __call__(self, obj: Any) -> bytes:
48
+ return dumps(obj, ensure_ascii=False).encode(self.charset)
49
+
50
+ @property
51
+ def format(self) -> str: return 'application/json'
52
+
53
+ @property
54
+ def charset(self) -> str: return 'utf-8'
55
+
56
+
57
+ class JavaScriptObjectNotationDecoder(Decoder):
58
+ """JavaScriptObjectNotation Decoder"""
59
+ def __call__(self, obj: bytes, format: str, charset: str) -> Any:
60
+ if format != 'application/json': raise ValueError('format is not support')
61
+ return loads(obj.decode(charset))