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