DefStream 0.1.16__56-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 +48 -0
  7. DefStream/Broker/Implements/RabbitMQ/Connection.py +542 -0
  8. DefStream/Broker/Implements/RabbitMQ/Consumer.py +359 -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 +319 -0
  13. DefStream/Broker/Implements/RabbitMQ/Serializer.py +61 -0
  14. DefStream/Broker/Implements/RabbitMQ/Stream.py +179 -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.16.dist-info/METADATA +1152 -0
  72. defstream-0.1.16.dist-info/RECORD +75 -0
  73. defstream-0.1.16.dist-info/WHEEL +6 -0
  74. defstream-0.1.16.dist-info/licenses/LICENSE +674 -0
  75. defstream-0.1.16.dist-info/top_level.txt +1 -0
@@ -0,0 +1,542 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ from DefStream.Broker import (
4
+ Connection as BaseConnection,
5
+ GetExchange,
6
+ GetQueue,
7
+ GetStream,
8
+ GetConsumer,
9
+ Declarable,
10
+ PrioritySupport,
11
+ EventHandler,
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
+
22
+ from pika import (
23
+ BlockingConnection,
24
+ ConnectionParameters,
25
+ PlainCredentials,
26
+ BasicProperties,
27
+ )
28
+ from pika.exceptions import *
29
+
30
+ from collections.abc import Mapping
31
+ from operator import (
32
+ eq,
33
+ ne,
34
+ )
35
+ from enum import Enum
36
+ from uuid import uuid4
37
+ from contextlib import contextmanager
38
+ from logging import getLogger
39
+
40
+ from typing import (
41
+ Any as AnyValue,
42
+ Iterator,
43
+ Union,
44
+ Optional,
45
+ )
46
+
47
+ __all__ = (
48
+ 'Connection',
49
+ 'ExchangeType',
50
+ 'Parameters',
51
+ 'All',
52
+ 'Any',
53
+ )
54
+
55
+
56
+ class ExchangeType(str, Enum):
57
+ Direct = 'direct'
58
+ FanOut = 'fanout'
59
+ Topic = 'topic'
60
+ Header = 'headers'
61
+ def __str__(self): return str(self.value)
62
+
63
+
64
+ class Parameters(Mapping):
65
+ """Bind Parameters Event Broker for RabbitMQ"""
66
+ def __init__(self, **kwargs):
67
+ self.args = kwargs
68
+ return
69
+
70
+ def __iter__(self) -> Iterator:
71
+ return self.args.__iter__()
72
+
73
+ def __reversed__(self) -> Iterator:
74
+ return self.args.__reversed__()
75
+
76
+ def __len__(self) -> int:
77
+ return self.args.__len__()
78
+
79
+ def __getitem__(
80
+ self,
81
+ key: AnyValue,
82
+ ) -> AnyValue:
83
+ return self.args.__getitem__(key)
84
+
85
+ def __contains__(self, key: object) -> bool:
86
+ return self.args.__contains__(key)
87
+
88
+ def __eq__(self, other) -> bool:
89
+ return eq(self.args, other)
90
+
91
+ def __ne__(self, other) -> bool:
92
+ return ne(self.args, other)
93
+
94
+ def keys(self) -> Iterator:
95
+ return self.args.keys()
96
+
97
+ def items(self):
98
+ return self.args.items()
99
+
100
+ def values(self):
101
+ return self.args.values()
102
+
103
+
104
+ class Connection(
105
+ BaseConnection,
106
+ GetExchange,
107
+ GetQueue,
108
+ GetStream,
109
+ GetConsumer,
110
+ Declarable,
111
+ PrioritySupport,
112
+ ):
113
+ """Connection of Event Broker for RabbitMQ"""
114
+ logger = getLogger('DefStream.Broker.Implements.RabbitMQ.Connection')
115
+
116
+ def __init__(self, conf: Configuration):
117
+ self.conf = conf
118
+ self.parameters = ConnectionParameters(
119
+ host=self.conf.host,
120
+ port=self.conf.port,
121
+ credentials=PlainCredentials(self.conf.username, self.conf.password),
122
+ virtual_host=self.conf.vhost,
123
+ ssl_options=self.conf.ssl,
124
+ blocked_connection_timeout=self.conf.timeout,
125
+ heartbeat=self.conf.heartbeat
126
+ )
127
+ self.connection = None
128
+ self.publisher = None
129
+ return
130
+
131
+ def __del__(self):
132
+ self.close()
133
+ return
134
+
135
+ def connect(self):
136
+ if self.connection and self.connection.is_open:
137
+ self.logger.debug('Reusing open connection')
138
+ return
139
+ self.logger.info('Connecting to RabbitMQ: host=%s port=%s vhost=%s', self.conf.host, self.conf.port, self.conf.vhost)
140
+ with self.observe('RabbitMQ connection'):
141
+ self.connection = BlockingConnection(parameters=self.parameters)
142
+ return
143
+
144
+ @contextmanager
145
+ def observe(
146
+ self,
147
+ operation: str,
148
+ *context,
149
+ ):
150
+ """이 클래스가 소유하는 I/O 경계만 기록하며 예외 내용은 노출하지 않는다."""
151
+ try:
152
+ yield
153
+ except Exception as error:
154
+ self.logger.error(operation + ' failed: error_type=%s', *context, type(error).__name__)
155
+ raise
156
+ else:
157
+ self.logger.info(operation + ' completed', *context)
158
+ return
159
+
160
+ def connected(self):
161
+ if self.connection is None or not self.connection.is_open:
162
+ self.logger.error('RabbitMQ connection is not open')
163
+ raise RuntimeError('Connect the RabbitMQ connection before use')
164
+ return self.connection
165
+
166
+ def declare(
167
+ self,
168
+ parameters: AnyValue,
169
+ ) -> None:
170
+ subscription = Subscription.parse(parameters)
171
+ self.logger.debug('Task queue declaration: queue=%s error_queue=%s max_priority=%s durable=True', subscription.queue, subscription.error, subscription.max_priority)
172
+ connection = self.connected()
173
+ with self.observe('Task queue declaration'):
174
+ channel = connection.channel()
175
+ try:
176
+ arguments = {}
177
+ if subscription.error is not None:
178
+ channel.queue_declare(
179
+ subscription.error,
180
+ durable=True,
181
+ )
182
+ arguments = {
183
+ 'x-dead-letter-exchange': '',
184
+ 'x-dead-letter-routing-key': subscription.error,
185
+ }
186
+ if subscription.max_priority is not None:
187
+ arguments['x-max-priority'] = subscription.max_priority
188
+ channel.queue_declare(
189
+ subscription.queue,
190
+ durable=True,
191
+ arguments=arguments,
192
+ )
193
+ finally:
194
+ if channel.is_open:
195
+ channel.close()
196
+ return
197
+
198
+ def priority_parameters(
199
+ self,
200
+ parameters: AnyValue,
201
+ ) -> Subscription:
202
+ """작업 기본값을 검증하고 선언·발행에 사용할 구독 설정을 반환한다."""
203
+ return Subscription.parse(parameters)
204
+
205
+ def emit(
206
+ self,
207
+ body: AnyValue,
208
+ parameters: AnyValue,
209
+ *,
210
+ priority: Optional[int] = None,
211
+ ) -> str:
212
+ subscription = Subscription.parse(parameters)
213
+ if priority is None:
214
+ priority = subscription.priority
215
+ subscription.validate_priority(priority)
216
+ connection = self.connected()
217
+ channel = self.publisher
218
+ if channel is None or not channel.is_open:
219
+ try:
220
+ channel = connection.channel()
221
+ channel.confirm_delivery()
222
+ self.logger.debug('Publisher channel created: publisher_confirm=True')
223
+ except Exception as error:
224
+ self.logger.error('Publisher channel creation failed: queue=%s error_type=%s', subscription.queue, type(error).__name__)
225
+ if channel is not None and channel.is_open:
226
+ channel.close()
227
+ raise
228
+ self.publisher = channel
229
+ else:
230
+ self.logger.debug('Reusing publisher channel: publisher_confirm=True')
231
+ id = uuid4().hex
232
+ self.logger.debug('Publishing directly: queue=%s message_id=%s priority=%s publisher_confirm=True', subscription.queue, id, priority)
233
+ try:
234
+ channel.basic_publish(
235
+ exchange='',
236
+ routing_key=subscription.queue,
237
+ body=self.conf.encode(body),
238
+ properties=BasicProperties(
239
+ message_id=id,
240
+ headers={},
241
+ content_type=self.conf.encode.format,
242
+ content_encoding=self.conf.encode.charset,
243
+ delivery_mode=2,
244
+ priority=priority,
245
+ ),
246
+ mandatory=True,
247
+ )
248
+ except Exception as error:
249
+ self.logger.error('Direct publication failed: queue=%s message_id=%s error_type=%s', subscription.queue, id, type(error).__name__)
250
+ raise
251
+ self.logger.debug('Direct publication confirmed: queue=%s message_id=%s', subscription.queue, id)
252
+ return id
253
+
254
+ def request(
255
+ self,
256
+ body: AnyValue,
257
+ parameters: AnyValue,
258
+ timeout: float = 5,
259
+ *,
260
+ priority: Optional[int] = None,
261
+ ) -> AnyValue:
262
+ subscription = Subscription.parse(parameters)
263
+ if priority is None:
264
+ priority = subscription.priority
265
+ subscription.validate_priority(priority)
266
+ self.connected()
267
+ queue = self.queue(subscription.queue)
268
+ self.logger.debug('Delegating RPC to Queue: queue=%s timeout=%s priority=%s', subscription.queue, timeout, priority)
269
+ try:
270
+ if priority is not None:
271
+ return queue.call(
272
+ body,
273
+ timeout=timeout,
274
+ priority=priority,
275
+ )
276
+ return queue.call(
277
+ body,
278
+ timeout=timeout,
279
+ )
280
+ finally:
281
+ try:
282
+ if queue.channel.is_open:
283
+ queue.channel.close()
284
+ self.logger.debug('RPC delegated channel cleanup completed: queue=%s', subscription.queue)
285
+ except Exception as error:
286
+ self.logger.error('RPC delegated channel cleanup failed: queue=%s error_type=%s', subscription.queue, type(error).__name__)
287
+ raise
288
+
289
+ def exchange(self, exchange: str) -> Exchange:
290
+ self.logger.debug('Creating Exchange: exchange=%s', exchange)
291
+ return Exchange(
292
+ self.connection,
293
+ self.conf.encode,
294
+ exchange,
295
+ decode=self.conf.decode,
296
+ rpc_parameters=self.parameters,
297
+ )
298
+
299
+ def queue(self, queue: str) -> Queue:
300
+ self.logger.debug('Creating Queue: queue=%s', queue)
301
+ return Queue(
302
+ self.connection,
303
+ self.conf.encode,
304
+ self.conf.decode,
305
+ queue,
306
+ rpc_parameters=self.parameters,
307
+ )
308
+
309
+ def stream(self, queue: str) -> Stream:
310
+ self.logger.debug('Creating Stream: queue=%s', queue)
311
+ return Stream(self.connection, self.conf.encode, self.conf.decode, queue)
312
+
313
+ def consumer(
314
+ self,
315
+ handler: Optional[EventHandler] = None,
316
+ qos: int = 1,
317
+ offset: int = None,
318
+ ) -> Consumer:
319
+ self.logger.debug('Creating Consumer: qos=%s offset=%s', qos, offset)
320
+ return Consumer(
321
+ self.connection,
322
+ self.conf.decode,
323
+ handler,
324
+ qos=qos,
325
+ offset=offset,
326
+ encode=self.conf.encode,
327
+ )
328
+
329
+ def close(self):
330
+ if self.connection and self.connection.is_open:
331
+ with self.observe('RabbitMQ connection close'):
332
+ self.connection.close()
333
+ self.connection = None
334
+ self.publisher = None
335
+ return
336
+
337
+ def create_exchange(
338
+ self,
339
+ name: str,
340
+ type: ExchangeType,
341
+ durable: bool = True,
342
+ autodelete: bool = False,
343
+ alter: Union[str, Exchange] = None,
344
+ ):
345
+ args = {}
346
+ if alter:
347
+ args['alternate-exchange'] = str(alter)
348
+ self.logger.debug('Exchange declaration: exchange=%s type=%s durable=%s auto_delete=%s alternate_exchange=%s', name, type, durable, autodelete, str(alter) if alter else None)
349
+ with self.observe('Exchange declaration: exchange=%s', name):
350
+ channel = self.connection.channel()
351
+ try:
352
+ channel.exchange_declare(
353
+ name,
354
+ str(type),
355
+ durable=durable,
356
+ auto_delete=autodelete,
357
+ arguments=args
358
+ )
359
+ finally:
360
+ if channel.is_open:
361
+ channel.close()
362
+ return
363
+
364
+ def delete_exchange(
365
+ self,
366
+ name: str,
367
+ ):
368
+ with self.observe('Exchange deletion: exchange=%s', name):
369
+ channel = self.connection.channel()
370
+ channel.exchange_delete(name)
371
+ return
372
+
373
+ def bind_exchange_to_exchange(
374
+ self,
375
+ src: Union[str, Exchange],
376
+ to: Union[str, Exchange],
377
+ event: str = None,
378
+ parameters: Parameters = None,
379
+ ):
380
+ with self.observe('Exchange binding: src=%s to=%s routing_key=%s', str(src), str(to), event):
381
+ channel = self.connection.channel()
382
+ channel.exchange_bind(
383
+ str(src),
384
+ str(to),
385
+ routing_key=event if event else '',
386
+ arguments=parameters
387
+ )
388
+ return
389
+
390
+ def unbind_exchange_to_exchange(
391
+ self,
392
+ src: Union[str, Exchange],
393
+ to: Union[str, Exchange],
394
+ event: str = None,
395
+ parameters: Parameters = None,
396
+ ):
397
+ with self.observe('Exchange unbinding: src=%s to=%s routing_key=%s', str(src), str(to), event):
398
+ channel = self.connection.channel()
399
+ channel.exchange_unbind(
400
+ str(src),
401
+ str(to),
402
+ routing_key=event if event else '',
403
+ arguments=parameters,
404
+ )
405
+ return
406
+
407
+ def create_queue(
408
+ self,
409
+ name: str,
410
+ expires: int = None,
411
+ ttl: int = None,
412
+ limit: int = None,
413
+ size: int = None,
414
+ maxPriority: int = None,
415
+ durable: bool = True,
416
+ autodelete: bool = False,
417
+ errorQueue: Union[str, Queue] = None,
418
+ errorExchange: Union[str, Exchange] = None,
419
+ ):
420
+ args = {}
421
+ if errorQueue:
422
+ args['x-dead-letter-exchange'] = ''
423
+ args['x-dead-letter-routing-key'] = str(errorQueue)
424
+ if errorExchange:
425
+ args['x-dead-letter-exchange'] = str(errorExchange)
426
+ if expires:
427
+ args['x-expires'] = expires
428
+ if ttl:
429
+ args['x-message-ttl'] = ttl
430
+ if limit:
431
+ args['x-max-length'] = limit
432
+ if size:
433
+ args['x-max-length-bytes'] = size
434
+ if maxPriority:
435
+ args['x-max-priority'] = maxPriority
436
+ self.logger.debug('Queue declaration options: queue=%s durable=%s auto_delete=%s expires=%s ttl=%s limit=%s size=%s max_priority=%s error_queue=%s error_exchange=%s', name, durable, autodelete, expires, ttl, limit, size, maxPriority, str(errorQueue) if errorQueue else None, str(errorExchange) if errorExchange else None)
437
+ with self.observe('Queue declaration: queue=%s', name):
438
+ channel = self.connection.channel()
439
+ channel.queue_declare(
440
+ name,
441
+ durable=durable,
442
+ auto_delete=autodelete,
443
+ arguments=args
444
+ )
445
+ return
446
+
447
+ def delete_queue(
448
+ self,
449
+ queue: str,
450
+ ):
451
+ with self.observe('Queue deletion: queue=%s', queue):
452
+ channel = self.connection.channel()
453
+ channel.queue_delete(queue)
454
+ return
455
+
456
+ def bind_exchange_to_queue(
457
+ self,
458
+ src: Union[str, Exchange],
459
+ to: Union[str, Queue],
460
+ event: str = None,
461
+ parameters: Parameters = None,
462
+ ):
463
+ with self.observe('Queue binding: exchange=%s queue=%s routing_key=%s', str(src), str(to), event):
464
+ channel = self.connection.channel()
465
+ channel.queue_bind(
466
+ str(to),
467
+ str(src),
468
+ routing_key=event if event else '',
469
+ arguments=parameters
470
+ )
471
+ return
472
+
473
+ def unbind_exchange_to_queue(
474
+ self,
475
+ src: Union[str, Exchange],
476
+ to: Union[str, Queue],
477
+ event: str = None,
478
+ parameters: Parameters = None,
479
+ ):
480
+ with self.observe('Queue unbinding: exchange=%s queue=%s routing_key=%s', str(src), str(to), event):
481
+ channel = self.connection.channel()
482
+ channel.queue_unbind(
483
+ str(to),
484
+ str(src),
485
+ routing_key=event if event else '',
486
+ arguments=parameters,
487
+ )
488
+ return
489
+
490
+ def create_stream(
491
+ self,
492
+ name: str,
493
+ size: int = None,
494
+ durable: bool = True,
495
+ autodelete: bool = False,
496
+ ):
497
+ args = {
498
+ 'x-queue-type': 'stream',
499
+ }
500
+ if size:
501
+ args['x-max-length-bytes'] = size
502
+ self.logger.debug('Stream declaration options: queue=%s durable=%s auto_delete=%s size=%s', name, durable, autodelete, size)
503
+ with self.observe('Stream declaration: queue=%s', name):
504
+ channel = self.connection.channel()
505
+ channel.queue_declare(
506
+ name,
507
+ durable=durable,
508
+ auto_delete=autodelete,
509
+ arguments=args
510
+ )
511
+ return
512
+
513
+ def delete_stream(
514
+ self,
515
+ queue: str,
516
+ ):
517
+ with self.observe('Stream deletion: queue=%s', queue):
518
+ channel = self.connection.channel()
519
+ channel.queue_delete(queue)
520
+ return
521
+
522
+
523
+ class All(Parameters):
524
+ """All Bind Parameters Event Broker for RabbitMQ"""
525
+ def __init__(self, **kwargs):
526
+ headers = {
527
+ 'x-match': 'all-with-x'
528
+ }
529
+ headers.update(kwargs)
530
+ super().__init__(**headers)
531
+ return
532
+
533
+
534
+ class Any(Parameters):
535
+ """Any Bind Parameters Event Broker for RabbitMQ"""
536
+ def __init__(self, **kwargs):
537
+ headers = {
538
+ 'x-match': 'any-with-x'
539
+ }
540
+ headers.update(kwargs)
541
+ super().__init__(**headers)
542
+ return