DefStream 0.1.18__74-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.
Files changed (75) hide show
  1. DefStream/Broker/Configuration.py +18 -0
  2. DefStream/Broker/Connection.py +141 -0
  3. DefStream/Broker/Consumer.py +59 -0
  4. DefStream/Broker/Errors.py +14 -0
  5. DefStream/Broker/Event.py +35 -0
  6. DefStream/Broker/Implements/RabbitMQ/Configuration.py +49 -0
  7. DefStream/Broker/Implements/RabbitMQ/Connection.py +547 -0
  8. DefStream/Broker/Implements/RabbitMQ/Consumer.py +358 -0
  9. DefStream/Broker/Implements/RabbitMQ/Event.py +127 -0
  10. DefStream/Broker/Implements/RabbitMQ/Exchange.py +278 -0
  11. DefStream/Broker/Implements/RabbitMQ/Policies.py +9 -0
  12. DefStream/Broker/Implements/RabbitMQ/Queue.py +321 -0
  13. DefStream/Broker/Implements/RabbitMQ/Serializer.py +75 -0
  14. DefStream/Broker/Implements/RabbitMQ/Stream.py +181 -0
  15. DefStream/Broker/Implements/RabbitMQ/Subscription.py +74 -0
  16. DefStream/Broker/Implements/RabbitMQ/__init__.py +47 -0
  17. DefStream/Broker/Implements/Redis/Configuration.py +59 -0
  18. DefStream/Broker/Implements/Redis/Connection.py +265 -0
  19. DefStream/Broker/Implements/Redis/Consumer.py +364 -0
  20. DefStream/Broker/Implements/Redis/Event.py +191 -0
  21. DefStream/Broker/Implements/Redis/Queue.py +284 -0
  22. DefStream/Broker/Implements/Redis/Serializer.py +48 -0
  23. DefStream/Broker/Implements/Redis/Subscription.py +46 -0
  24. DefStream/Broker/Implements/Redis/__init__.py +29 -0
  25. DefStream/Broker/Implements/__init__.py +1 -0
  26. DefStream/Broker/Policies.py +25 -0
  27. DefStream/Broker/Priority.py +26 -0
  28. DefStream/Broker/Protocol.py +45 -0
  29. DefStream/Broker/__init__.py +59 -0
  30. DefStream/Descriptor/DataObject.py +409 -0
  31. DefStream/Descriptor/Types.py +248 -0
  32. DefStream/Descriptor/Value.py +188 -0
  33. DefStream/Descriptor/__init__.py +172 -0
  34. DefStream/Inspector/Extension.py +69 -0
  35. DefStream/Inspector/Extensions/Console.html +373 -0
  36. DefStream/Inspector/Extensions/Console.py +415 -0
  37. DefStream/Inspector/Extensions/Metrics.py +316 -0
  38. DefStream/Inspector/Extensions/Ping.py +107 -0
  39. DefStream/Inspector/Extensions/Specifications.py +176 -0
  40. DefStream/Inspector/Extensions/Status.html +206 -0
  41. DefStream/Inspector/Extensions/Status.py +124 -0
  42. DefStream/Inspector/Extensions/__init__.py +13 -0
  43. DefStream/Inspector/Inspector.py +347 -0
  44. DefStream/Inspector/Response.py +14 -0
  45. DefStream/Inspector/__init__.py +9 -0
  46. DefStream/Validator/Pattern.py +36 -0
  47. DefStream/Validator/Patterns/Array.py +30 -0
  48. DefStream/Validator/Patterns/Common.py +92 -0
  49. DefStream/Validator/Patterns/Compare.py +180 -0
  50. DefStream/Validator/Patterns/Condition.py +60 -0
  51. DefStream/Validator/Patterns/DataObject.py +85 -0
  52. DefStream/Validator/Patterns/DateTime.py +111 -0
  53. DefStream/Validator/Patterns/Number.py +43 -0
  54. DefStream/Validator/Patterns/Object.py +150 -0
  55. DefStream/Validator/Patterns/Size.py +110 -0
  56. DefStream/Validator/Patterns/String.py +85 -0
  57. DefStream/Validator/Patterns/Type.py +350 -0
  58. DefStream/Validator/Patterns/__init__.py +137 -0
  59. DefStream/Validator/Validate.py +29 -0
  60. DefStream/Validator/Validator.py +30 -0
  61. DefStream/Validator/__init__.py +11 -0
  62. DefStream/Worker/Executor.py +371 -0
  63. DefStream/Worker/Message.py +180 -0
  64. DefStream/Worker/Pool.py +245 -0
  65. DefStream/Worker/Run.py +357 -0
  66. DefStream/Worker/Specification.py +87 -0
  67. DefStream/Worker/Startup.py +174 -0
  68. DefStream/Worker/Worker.py +421 -0
  69. DefStream/Worker/__init__.py +47 -0
  70. DefStream/__init__.py +0 -0
  71. defstream-0.1.18.dist-info/METADATA +92 -0
  72. defstream-0.1.18.dist-info/RECORD +75 -0
  73. defstream-0.1.18.dist-info/WHEEL +6 -0
  74. defstream-0.1.18.dist-info/licenses/LICENSE +674 -0
  75. defstream-0.1.18.dist-info/top_level.txt +1 -0
@@ -0,0 +1,358 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ from DefStream.Broker import (
4
+ Consumer as BaseConsumer,
5
+ EventHandler,
6
+ Event as BaseEvent,
7
+ EventError,
8
+ )
9
+
10
+ from .Serializer import (
11
+ Decoder,
12
+ Encoder,
13
+ JavaScriptObjectNotationEncoder,
14
+ )
15
+ from logging import getLogger
16
+
17
+ from .Event import Event
18
+ from .Policies import Requeue
19
+ from .Subscription import Subscription
20
+ from typing import (
21
+ Any,
22
+ Callable,
23
+ Optional,
24
+ )
25
+
26
+ from pika import (
27
+ BasicProperties,
28
+ BlockingConnection,
29
+ )
30
+ from pika.exceptions import UnroutableError
31
+
32
+ __all__ = (
33
+ 'Consumer',
34
+ )
35
+
36
+ class Consumer(BaseConsumer):
37
+ """Consumer of Event Broker for RabbitMQ"""
38
+ logger = getLogger('DefStream.Broker.RabbitMQ.Consumer')
39
+
40
+ def __init__(
41
+ self,
42
+ connection: BlockingConnection,
43
+ decode: Decoder,
44
+ handler: Optional[EventHandler] = None,
45
+ qos: int = 1,
46
+ offset: Optional[int] = None,
47
+ encode: Optional[Encoder] = None,
48
+ ):
49
+ self.connection = connection
50
+ self.decode = decode
51
+ self.qos = qos
52
+ try:
53
+ self.channel = self.connection.channel()
54
+ self.channel.basic_qos(
55
+ prefetch_count=qos,
56
+ global_qos=False,
57
+ )
58
+ except Exception as error:
59
+ self.logger.error('Consumption channel initialization failed: qos=%s error_type=%s', qos, type(error).__name__)
60
+ raise
61
+ self.logger.debug('Consumption channel created: qos=%s offset=%s', qos, offset)
62
+ self.offset = offset
63
+ self.handler = handler
64
+ self.encode = encode if encode is not None else JavaScriptObjectNotationEncoder()
65
+ self.response_channel = None
66
+ self.queues = set()
67
+ self.stopped = False
68
+ self.callback_error = None
69
+ return
70
+
71
+ def __del__(self):
72
+ self.close()
73
+ return
74
+
75
+ def subs(
76
+ self,
77
+ queue: Any,
78
+ handler: Optional[EventHandler] = None,
79
+ ) -> None:
80
+ if handler is None:
81
+ handler = self.handler
82
+ if handler is None:
83
+ raise ValueError('subs requires a handler or a default handler')
84
+ if isinstance(
85
+ queue,
86
+ str,
87
+ ):
88
+ subscription = Subscription(
89
+ queue=queue,
90
+ offset=self.offset,
91
+ )
92
+ else:
93
+ subscription = Subscription.parse(queue)
94
+ if subscription.queue in self.queues:
95
+ raise ValueError('A Run is already subscribed to queue {}'.format(subscription.queue))
96
+ qos = self.qos if subscription.qos is None else subscription.qos
97
+ self.logger.debug('Applying subscription: queue=%s qos=%s offset=%s', subscription.queue, qos, subscription.offset)
98
+ try:
99
+ self.channel.basic_qos(
100
+ prefetch_count=qos,
101
+ global_qos=False,
102
+ )
103
+ tag = self.channel.basic_consume(
104
+ subscription.queue,
105
+ Callback(
106
+ self.decode,
107
+ handler,
108
+ subscription.queue,
109
+ logger=self.logger,
110
+ owner=self,
111
+ ),
112
+ auto_ack=False,
113
+ exclusive=False,
114
+ arguments={'x-stream-offset': subscription.offset} if subscription.offset is not None else None,
115
+ )
116
+ except Exception as error:
117
+ self.logger.error('Subscription failed: queue=%s error_type=%s', subscription.queue, type(error).__name__)
118
+ raise
119
+ self.queues.add(subscription.queue)
120
+ self.logger.debug('Subscription applied: queue=%s consumer_tag=%s', subscription.queue, tag)
121
+ return
122
+
123
+ def call(self, callback: Callable[[], None]) -> None:
124
+ self.logger.debug('Scheduling callback on connection owner thread')
125
+ try:
126
+ self.connection.add_callback_threadsafe(callback)
127
+ except Exception as error:
128
+ self.logger.error('Callback scheduling failed: error_type=%s', type(error).__name__)
129
+ raise
130
+ self.logger.debug('Callback scheduled on connection owner thread')
131
+ return
132
+
133
+ def fail(
134
+ self,
135
+ event: BaseEvent,
136
+ policy: Optional[EventError] = None,
137
+ ) -> None:
138
+ self.logger.debug('Task failure policy selected: policy=%s', type(policy).__name__ if policy is not None else 'Requeue')
139
+ if isinstance(
140
+ event,
141
+ Event,
142
+ ) and event.properties.reply_to:
143
+ event = FailedRequest(
144
+ self,
145
+ event,
146
+ )
147
+ (policy if policy is not None else Requeue())(event)
148
+ return
149
+
150
+ def complete(
151
+ self,
152
+ event: BaseEvent,
153
+ result: Any,
154
+ ) -> None:
155
+ self.logger.debug('Task completion policy selected: ack')
156
+ if isinstance(
157
+ event,
158
+ Event,
159
+ ) and event.properties.reply_to:
160
+ self.logger.debug('Building RPC success response: queue=%s correlation_id=%s', event.queue, event.properties.correlation_id)
161
+ self.respond(
162
+ event,
163
+ {
164
+ 'ok': True,
165
+ 'result': result,
166
+ },
167
+ )
168
+ event.ack()
169
+ return
170
+
171
+ def respond(
172
+ self,
173
+ event: Event,
174
+ response: Any,
175
+ ) -> None:
176
+ if not event.properties.correlation_id:
177
+ self.logger.error('RPC response validation failed: missing correlation_id')
178
+ raise ValueError('RPC request requires correlation_id')
179
+ channel = self.response_channel
180
+ self.logger.debug('Attempting RPC response: correlation_id=%s reply_to=%s', event.properties.correlation_id, event.properties.reply_to)
181
+ if channel is None:
182
+ try:
183
+ channel = self.connection.channel()
184
+ channel.confirm_delivery()
185
+ self.response_channel = channel
186
+ self.logger.debug('RPC response channel created: publisher_confirm=True')
187
+ except Exception as error:
188
+ self.logger.error('RPC response channel creation failed: correlation_id=%s error_type=%s', event.properties.correlation_id, type(error).__name__)
189
+ raise
190
+ else:
191
+ self.logger.debug('Reusing RPC response channel')
192
+ try:
193
+ channel.basic_publish(
194
+ exchange='',
195
+ routing_key=event.properties.reply_to,
196
+ body=self.encode(response),
197
+ properties=BasicProperties(
198
+ correlation_id=event.properties.correlation_id,
199
+ content_type=self.encode.format,
200
+ content_encoding=self.encode.charset,
201
+ ),
202
+ mandatory=True,
203
+ )
204
+ except UnroutableError:
205
+ self.logger.warning(
206
+ 'RPC reply is unroutable; settling request without a response: correlation_id=%s reply_to=%s',
207
+ event.properties.correlation_id,
208
+ event.properties.reply_to,
209
+ )
210
+ except Exception as error:
211
+ self.logger.error('RPC response publication failed: correlation_id=%s error_type=%s', event.properties.correlation_id, type(error).__name__)
212
+ raise
213
+ else:
214
+ self.logger.debug('RPC response published: correlation_id=%s', event.properties.correlation_id)
215
+ return
216
+
217
+ def close(self) -> None:
218
+ response_channel = getattr(self, 'response_channel', None)
219
+ try:
220
+ if response_channel is not None and response_channel.is_open:
221
+ response_channel.close()
222
+ self.logger.debug('RPC response channel closed')
223
+ channel = getattr(self, 'channel', None)
224
+ if channel is not None and channel.is_open:
225
+ channel.close()
226
+ self.logger.debug('Consumption channel closed')
227
+ except Exception as error:
228
+ self.logger.error('Channel close failed: error_type=%s', type(error).__name__)
229
+ raise
230
+ return
231
+
232
+ def run(self):
233
+ self.logger.debug('Starting consumption loop')
234
+ try:
235
+ self.channel.start_consuming()
236
+ except Exception as error:
237
+ # 정산 오류는 예약 콜백에서 루프로 전파되어도 Event의 기록을 재출력하지 않는다.
238
+ owned_boundaries = (
239
+ Event.ack.__code__,
240
+ Event.nack.__code__,
241
+ Event.reject.__code__,
242
+ Consumer.respond.__code__,
243
+ Consumer.stop.__code__,
244
+ )
245
+ trace = error.__traceback__
246
+ observed = error is self.callback_error
247
+ while trace is not None:
248
+ if trace.tb_frame.f_code in owned_boundaries:
249
+ observed = True
250
+ break
251
+ trace = trace.tb_next
252
+ if not observed:
253
+ self.logger.error('Consumption loop failed: error_type=%s', type(error).__name__)
254
+ raise
255
+ finally:
256
+ self.callback_error = None
257
+ self.logger.debug('Consumption loop ended')
258
+ return
259
+
260
+ def stop(self):
261
+ if not self.stopped:
262
+ self.stopped = True
263
+ self.logger.debug('Consumption loop stop requested')
264
+ try:
265
+ self.channel.stop_consuming()
266
+ except Exception as error:
267
+ self.logger.error('Failed to stop consumption loop: error_type=%s', type(error).__name__)
268
+ raise
269
+ self.logger.debug('Consumption loop stopped')
270
+ return
271
+
272
+
273
+ class FailedRequest(BaseEvent):
274
+ def __init__(
275
+ self,
276
+ consumer: Consumer,
277
+ event: Event,
278
+ ):
279
+ self.consumer = consumer
280
+ self.event = event
281
+
282
+ @property
283
+ def body(self) -> Any:
284
+ return self.event.body
285
+
286
+ def ack(self) -> None:
287
+ self.event.ack()
288
+
289
+ def nack(self) -> None:
290
+ self.event.nack()
291
+
292
+ def reject(self) -> None:
293
+ self.consumer.logger.debug('Building RPC failure response: queue=%s correlation_id=%s', self.event.queue, self.event.properties.correlation_id)
294
+ self.consumer.respond(
295
+ self.event,
296
+ {
297
+ 'ok': False,
298
+ 'error': {
299
+ 'code': 'ProcessingError',
300
+ 'message': 'Request processing failed',
301
+ },
302
+ },
303
+ )
304
+ self.event.reject()
305
+ return
306
+
307
+
308
+ class Callback(object):
309
+ def __init__(
310
+ self,
311
+ decode: Decoder,
312
+ handler: EventHandler,
313
+ queue: str,
314
+ logger=None,
315
+ owner=None,
316
+ ):
317
+ self.decode = decode
318
+ self.handler = handler
319
+ self.queue = queue
320
+ self.logger = logger if logger is not None else Consumer.logger
321
+ self.owner = owner
322
+ return
323
+ def __call__(
324
+ self,
325
+ channel,
326
+ method,
327
+ properties,
328
+ body,
329
+ ):
330
+ try:
331
+ return self.deliver(channel, method, properties, body)
332
+ except Exception as error:
333
+ if self.owner is not None:
334
+ self.owner.callback_error = error
335
+ raise
336
+
337
+ def deliver(
338
+ self,
339
+ channel,
340
+ method,
341
+ properties,
342
+ body,
343
+ ):
344
+ self.logger.debug('Message received: queue=%s consumer_tag=%s delivery_tag=%s message_id=%s', self.queue, method.consumer_tag, method.delivery_tag, properties.message_id)
345
+ try:
346
+ decoded = self.decode(body, properties.content_type, properties.content_encoding)
347
+ except Exception as error:
348
+ self.logger.error('Message decoding failed: queue=%s delivery_tag=%s error_type=%s', self.queue, method.delivery_tag, type(error).__name__)
349
+ raise
350
+ self.logger.debug('Message decoded; dispatching to handler: queue=%s delivery_tag=%s', self.queue, method.delivery_tag)
351
+ return self.handler(Event(
352
+ channel,
353
+ self.queue,
354
+ method.consumer_tag,
355
+ method.delivery_tag,
356
+ properties,
357
+ decoded,
358
+ ))
@@ -0,0 +1,127 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ from DefStream.Broker import (
4
+ Event as BaseEvent,
5
+ EventHandler as BaseEventHandler,
6
+ )
7
+
8
+ from abc import abstractmethod
9
+ from logging import getLogger
10
+
11
+ __all__ = (
12
+ 'Event',
13
+ 'EventHandler',
14
+ )
15
+
16
+
17
+ class Event(BaseEvent):
18
+ """Event of Event Broker for RabbitMQ"""
19
+ logger = getLogger('DefStream.Broker.RabbitMQ.Event')
20
+
21
+ def __init__(
22
+ self,
23
+ channel,
24
+ queue,
25
+ consumer,
26
+ transaction,
27
+ properties,
28
+ body,
29
+ ):
30
+ self.channel = channel
31
+ self.queue = queue
32
+ self.consumer = consumer
33
+ self.transaction = transaction
34
+ self.properties = properties
35
+ self.props = properties.headers or {}
36
+ self.payload = body
37
+ return
38
+
39
+ def __repr__(self):
40
+ return 'Event(id={}, body={}, type={}, headers={})'.format(
41
+ self.properties.message_id,
42
+ self.payload,
43
+ self.properties.type,
44
+ self.props,
45
+ )
46
+
47
+ def ack(self):
48
+ self.logger.debug('ack requested: queue=%s delivery_tag=%s message_id=%s', self.queue, self.transaction, self.id)
49
+ try:
50
+ self.channel.basic_ack(self.transaction)
51
+ except Exception as error:
52
+ self.logger.error('ack failed: queue=%s delivery_tag=%s error_type=%s', self.queue, self.transaction, type(error).__name__)
53
+ raise
54
+ self.logger.debug('ack broker API call completed: queue=%s delivery_tag=%s', self.queue, self.transaction)
55
+ return
56
+
57
+ def nack(self):
58
+ self.logger.debug('nack requested: queue=%s delivery_tag=%s message_id=%s requeue=True', self.queue, self.transaction, self.id)
59
+ try:
60
+ self.channel.basic_nack(
61
+ self.transaction,
62
+ requeue=True,
63
+ )
64
+ except Exception as error:
65
+ self.logger.error('nack failed: queue=%s delivery_tag=%s error_type=%s', self.queue, self.transaction, type(error).__name__)
66
+ raise
67
+ self.logger.debug('nack broker API call completed: queue=%s delivery_tag=%s', self.queue, self.transaction)
68
+ return
69
+
70
+ def reject(self):
71
+ self.logger.debug('reject requested: queue=%s delivery_tag=%s message_id=%s requeue=False', self.queue, self.transaction, self.id)
72
+ try:
73
+ self.channel.basic_reject(
74
+ self.transaction,
75
+ requeue=False,
76
+ )
77
+ except Exception as error:
78
+ self.logger.error('reject failed: queue=%s delivery_tag=%s error_type=%s', self.queue, self.transaction, type(error).__name__)
79
+ raise
80
+ self.logger.debug('reject broker API call completed: queue=%s delivery_tag=%s', self.queue, self.transaction)
81
+ return
82
+
83
+ def headers(self):
84
+ return self.props
85
+
86
+ def header(
87
+ self,
88
+ key,
89
+ ):
90
+ if key not in self.props:
91
+ return None
92
+ return self.props[key]
93
+
94
+ @property
95
+ def src(self):
96
+ return self.queue
97
+
98
+ @property
99
+ def id(self):
100
+ return self.properties.message_id
101
+
102
+ @property
103
+ def type(self):
104
+ return self.properties.type
105
+
106
+ @property
107
+ def body(self):
108
+ return self.payload
109
+
110
+ @property
111
+ def priority(self):
112
+ if not hasattr(
113
+ self.properties,
114
+ 'priority',
115
+ ):
116
+ return None
117
+ return self.properties.priority
118
+
119
+
120
+ class EventHandler(BaseEventHandler):
121
+ """Event Handler Interface of Event Broker for RabbitMQ"""
122
+ @abstractmethod
123
+ def __call__(
124
+ self,
125
+ event: Event,
126
+ ):
127
+ raise NotImplementedError('{} must be implemented __call__'.format(self.__class__.__name__))