DefStream 0.1.17__66-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.17.dist-info/METADATA +92 -0
  72. defstream-0.1.17.dist-info/RECORD +75 -0
  73. defstream-0.1.17.dist-info/WHEEL +6 -0
  74. defstream-0.1.17.dist-info/licenses/LICENSE +674 -0
  75. defstream-0.1.17.dist-info/top_level.txt +1 -0
@@ -0,0 +1,179 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ from DefStream.Broker import (
4
+ Sendable,
5
+ Readable,
6
+ )
7
+ from .Serializer import (
8
+ Encoder,
9
+ Decoder,
10
+ )
11
+ from .Event import Event
12
+
13
+ from pika import (
14
+ BlockingConnection,
15
+ BasicProperties,
16
+ )
17
+
18
+ from time import time
19
+ from logging import getLogger
20
+ from uuid import uuid4
21
+
22
+ from typing import (
23
+ Dict,
24
+ Iterable,
25
+ )
26
+
27
+ __all__ = (
28
+ 'Queue'
29
+ )
30
+
31
+
32
+ class Stream(
33
+ Sendable,
34
+ Readable,
35
+ ):
36
+ """Stream of Event Broker for RabbitMQ"""
37
+ logger = getLogger('DefStream.Broker.Implements.RabbitMQ.Stream')
38
+
39
+ def __init__(
40
+ self,
41
+ connection: BlockingConnection,
42
+ encode: Encoder,
43
+ decode: Decoder,
44
+ name: str = None,
45
+ ):
46
+ self.connection = connection
47
+ self.encode = encode
48
+ self.decode = decode
49
+ try:
50
+ self.channel = self.connection.channel()
51
+ except Exception as error:
52
+ self.logger.error('Stream channel creation failed: queue=%s error_type=%s', name, type(error).__name__)
53
+ raise
54
+ self.channel.auto_decode = False
55
+ self.queue = name
56
+ self.event = None
57
+ return
58
+
59
+ def __del__(self):
60
+ channel = getattr(self, 'channel', None)
61
+ try:
62
+ if channel and channel.is_open:
63
+ channel.close()
64
+ self.logger.debug('Stream channel cleanup completed: queue=%s', self.queue)
65
+ except Exception as error:
66
+ self.logger.error('Stream channel cleanup failed: error_type=%s', type(error).__name__)
67
+ raise
68
+ return
69
+
70
+ def __str__(self): return self.queue
71
+
72
+ def send(
73
+ self,
74
+ body,
75
+ event: str = None,
76
+ headers: Dict = {},
77
+ timestamp: int = None,
78
+ id: str = None,
79
+ ):
80
+ if not timestamp: timestamp = int(time())
81
+ if not id: id = uuid4().hex
82
+ properties = BasicProperties(
83
+ type=event if event else '',
84
+ headers=headers,
85
+ content_type=self.encode.format,
86
+ content_encoding=self.encode.charset,
87
+ timestamp=timestamp,
88
+ message_id=id,
89
+ )
90
+ self.logger.debug('Publishing Stream message: queue=%s message_id=%s publisher_confirm=False', self.queue, id)
91
+ try:
92
+ self.channel.basic_publish(
93
+ exchange='',
94
+ routing_key=self.queue,
95
+ properties=properties,
96
+ body=self.encode(body)
97
+ )
98
+ except Exception as error:
99
+ self.logger.error('Stream publication failed: queue=%s message_id=%s error_type=%s', self.queue, id, type(error).__name__)
100
+ raise
101
+ self.logger.debug('Stream publish API call completed: queue=%s message_id=%s', self.queue, id)
102
+ return id
103
+
104
+ def read(self, offset: int = 0, timeout: int = None) -> Iterable[Event]:
105
+ channel = self.channel
106
+ self.logger.debug('Starting Stream read: queue=%s offset=%s timeout_ms=%s qos=1', self.queue, offset, timeout)
107
+ try:
108
+ channel.basic_qos(0, 1, False)
109
+ except Exception as error:
110
+ self.logger.error('Failed to apply Stream QoS: queue=%s error_type=%s', self.queue, type(error).__name__)
111
+ raise
112
+
113
+ deadline = time() + (timeout / 1000) if timeout else None
114
+
115
+ failure_observed = False
116
+ try:
117
+ for method, properties, body in channel.consume(
118
+ queue=self.queue,
119
+ auto_ack=False,
120
+ inactivity_timeout=1,
121
+ arguments={'x-stream-offset': offset},
122
+ ):
123
+ if method is None:
124
+ if deadline and time() >= deadline:
125
+ break
126
+ continue
127
+
128
+ try:
129
+ decoded = self.decode(body, properties.content_type, properties.content_encoding)
130
+ except Exception as error:
131
+ failure_observed = True
132
+ self.logger.error('Stream decoding failed: queue=%s delivery_tag=%s error_type=%s', self.queue, method.delivery_tag, type(error).__name__)
133
+ raise
134
+ self.logger.debug('Stream message received and decoded: queue=%s consumer_tag=%s delivery_tag=%s message_id=%s', self.queue, method.consumer_tag, method.delivery_tag, properties.message_id)
135
+ event = Event(
136
+ channel,
137
+ self.queue,
138
+ method.consumer_tag,
139
+ method.delivery_tag,
140
+ properties,
141
+ decoded,
142
+ )
143
+
144
+ try:
145
+ failure_observed = True
146
+ yield event
147
+ failure_observed = False
148
+ self.logger.debug('Stream ack requested: queue=%s delivery_tag=%s', self.queue, method.delivery_tag)
149
+ try:
150
+ channel.basic_ack(delivery_tag=method.delivery_tag)
151
+ except Exception as error:
152
+ failure_observed = True
153
+ self.logger.error('Stream ack failed: queue=%s delivery_tag=%s error_type=%s', self.queue, method.delivery_tag, type(error).__name__)
154
+ raise
155
+ self.logger.debug('Stream ack API call completed: queue=%s delivery_tag=%s', self.queue, method.delivery_tag)
156
+ except Exception:
157
+ failure_observed = True
158
+ self.logger.warning('Attempting requeue after Stream processing exception: queue=%s delivery_tag=%s', self.queue, method.delivery_tag)
159
+ self.logger.debug('Stream nack requested: queue=%s delivery_tag=%s requeue=True', self.queue, method.delivery_tag)
160
+ try:
161
+ channel.basic_nack(delivery_tag=method.delivery_tag, requeue=True)
162
+ except Exception as error:
163
+ self.logger.error('Stream nack failed: queue=%s delivery_tag=%s error_type=%s', self.queue, method.delivery_tag, type(error).__name__)
164
+ raise
165
+ self.logger.debug('Stream nack API call completed: queue=%s delivery_tag=%s', self.queue, method.delivery_tag)
166
+ raise
167
+ except Exception as error:
168
+ if not failure_observed:
169
+ self.logger.error('Stream consumption loop failed: queue=%s error_type=%s', self.queue, type(error).__name__)
170
+ raise
171
+ finally:
172
+ self.logger.debug('Ending Stream read loop; requesting Consumer cancellation: queue=%s', self.queue)
173
+ try:
174
+ channel.cancel()
175
+ except Exception as error:
176
+ self.logger.error('Stream Consumer cancellation failed: queue=%s error_type=%s', self.queue, type(error).__name__)
177
+ raise
178
+ self.logger.debug('Stream Consumer cancelled: queue=%s', self.queue)
179
+ return
@@ -0,0 +1,74 @@
1
+ from collections.abc import Mapping
2
+ from dataclasses import dataclass
3
+ from typing import (
4
+ Any,
5
+ Optional,
6
+ )
7
+ from DefStream.Broker import validate_priority
8
+
9
+ __all__ = (
10
+ 'Subscription',
11
+ )
12
+
13
+
14
+ @dataclass(frozen=True)
15
+ class Subscription:
16
+ queue: str
17
+ offset: Optional[int] = None
18
+ qos: Optional[int] = None
19
+ error: Optional[str] = None
20
+ max_priority: Optional[int] = None
21
+ priority: Optional[int] = None
22
+
23
+ def __post_init__(self):
24
+ if self.priority is not None and self.max_priority is None:
25
+ object.__setattr__(
26
+ self,
27
+ 'max_priority',
28
+ 255,
29
+ )
30
+ validate_priority(
31
+ self.max_priority,
32
+ 255,
33
+ minimum=1,
34
+ name='max_priority',
35
+ )
36
+ self.validate_priority(self.priority)
37
+ if not isinstance(self.queue, str) or not self.queue:
38
+ raise ValueError('Subscription queue must be a non-empty string')
39
+ if self.error is not None:
40
+ if not isinstance(
41
+ self.error,
42
+ str,
43
+ ) or not self.error:
44
+ raise ValueError('Subscription error must be a non-empty string or None')
45
+ if self.offset is not None and (isinstance(self.offset, bool) or not isinstance(self.offset, int)):
46
+ raise ValueError('Subscription offset must be an integer')
47
+ if self.qos is not None:
48
+ if isinstance(
49
+ self.qos,
50
+ bool,
51
+ ) or not isinstance(
52
+ self.qos,
53
+ int,
54
+ ) or not 0 <= self.qos <= 65535:
55
+ raise ValueError('Subscription qos must be an integer between 0 and 65535')
56
+ return
57
+
58
+ def validate_priority(
59
+ self,
60
+ priority: Optional[int],
61
+ ) -> None:
62
+ validate_priority(
63
+ priority,
64
+ 255 if self.max_priority is None else self.max_priority,
65
+ )
66
+ return
67
+
68
+ @classmethod
69
+ def parse(cls, parameters: Any) -> 'Subscription':
70
+ if isinstance(parameters, cls):
71
+ return parameters
72
+ if isinstance(parameters, Mapping):
73
+ return cls(**dict(parameters))
74
+ raise TypeError('RabbitMQ subscriptions require a Subscription or mapping')
@@ -0,0 +1,47 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ from .Configuration import Configuration
4
+ from .Connection import (
5
+ Connection,
6
+ ExchangeType,
7
+ Parameters,
8
+ All,
9
+ Any,
10
+ )
11
+ from .Exchange import Exchange
12
+ from .Queue import Queue
13
+ from .Stream import Stream
14
+ from .Consumer import Consumer
15
+ from .Subscription import Subscription
16
+ from .Policies import (
17
+ Requeue,
18
+ Reject,
19
+ )
20
+ from .Event import (
21
+ Event,
22
+ EventHandler,
23
+ )
24
+ from .Serializer import (
25
+ Encoder,
26
+ Decoder,
27
+ )
28
+
29
+ __all__ = (
30
+ 'Configuration',
31
+ 'Connection',
32
+ 'ExchangeType',
33
+ 'Parameters',
34
+ 'All',
35
+ 'Any',
36
+ 'Exchange',
37
+ 'Queue',
38
+ 'Stream',
39
+ 'Consumer',
40
+ 'Subscription',
41
+ 'Requeue',
42
+ 'Reject',
43
+ 'Event',
44
+ 'EventHandler',
45
+ 'Encoder',
46
+ 'Decoder',
47
+ )
@@ -0,0 +1,59 @@
1
+ from enum import Enum
2
+ from typing import Optional
3
+
4
+ from DefStream.Broker import Configuration as BaseConfiguration
5
+ from .Serializer import (
6
+ Decoder,
7
+ Encoder,
8
+ JavaScriptObjectNotationDecoder,
9
+ JavaScriptObjectNotationEncoder,
10
+ )
11
+
12
+ __all__ = (
13
+ 'Configuration',
14
+ 'ConnectionType',
15
+ )
16
+
17
+
18
+ class ConnectionType(Enum):
19
+ Pool = 'pool'
20
+ Cluster = 'cluster'
21
+
22
+
23
+ class Configuration(BaseConfiguration):
24
+ def __init__(
25
+ self,
26
+ host: str = '127.0.0.1',
27
+ port: int = 6379,
28
+ username: Optional[str] = None,
29
+ password: Optional[str] = None,
30
+ secure: bool = False,
31
+ type: ConnectionType = ConnectionType.Pool,
32
+ max: int = 10,
33
+ database: int = 0,
34
+ timeout: float = 5,
35
+ encode: Optional[Encoder] = None,
36
+ decode: Optional[Decoder] = None,
37
+ ):
38
+ if not isinstance(
39
+ type,
40
+ ConnectionType,
41
+ ):
42
+ raise ValueError('Redis connection type must be a ConnectionType')
43
+ if max < 1:
44
+ raise ValueError('Redis max connections must be positive')
45
+ if database < 0 or (type == ConnectionType.Cluster and database != 0):
46
+ raise ValueError('Redis database must be non-negative; Cluster requires database 0')
47
+ if timeout <= 0:
48
+ raise ValueError('Redis connection timeout must be positive')
49
+ self.host = host
50
+ self.port = port
51
+ self.username = username
52
+ self.password = password
53
+ self.secure = secure
54
+ self.type = type
55
+ self.max = max
56
+ self.database = database
57
+ self.timeout = timeout
58
+ self.encode = encode if encode is not None else JavaScriptObjectNotationEncoder()
59
+ self.decode = decode if decode is not None else JavaScriptObjectNotationDecoder()
@@ -0,0 +1,265 @@
1
+ from logging import getLogger
2
+ from typing import (
3
+ Any,
4
+ Optional,
5
+ Union,
6
+ )
7
+
8
+ from redis import Redis
9
+ from redis.cluster import RedisCluster
10
+
11
+ from DefStream.Broker import (
12
+ Connection as BaseConnection,
13
+ GetQueue,
14
+ GetStream,
15
+ GetConsumer,
16
+ Declarable,
17
+ Emittable,
18
+ EventHandler,
19
+ )
20
+ from .Configuration import (
21
+ Configuration,
22
+ ConnectionType,
23
+ )
24
+ from .Consumer import Consumer
25
+ from .Queue import Queue
26
+ from .Subscription import Subscription
27
+
28
+ __all__ = (
29
+ 'Connection',
30
+ )
31
+
32
+
33
+ class Connection(
34
+ BaseConnection,
35
+ GetQueue,
36
+ GetStream,
37
+ GetConsumer,
38
+ Declarable,
39
+ Emittable,
40
+ ):
41
+ logger = getLogger('DefStream.Broker.Implements.Redis.Connection')
42
+
43
+ def __init__(
44
+ self,
45
+ conf: Configuration,
46
+ ):
47
+ self.conf = conf
48
+ self.connection: Optional[Union[Redis, RedisCluster]] = None
49
+
50
+ def connect(self) -> None:
51
+ if self.connection is not None:
52
+ self.logger.debug('Reusing existing Redis connection')
53
+ return
54
+ connection_type = self.conf.type.value
55
+ self.logger.info(
56
+ 'Connecting to Redis: type=%s',
57
+ connection_type,
58
+ )
59
+ self.logger.debug(
60
+ 'Redis connection settings: host=%s port=%s database=%s TLS=%s max=%s timeout=%ss',
61
+ self.conf.host,
62
+ self.conf.port,
63
+ self.conf.database,
64
+ self.conf.secure,
65
+ self.conf.max,
66
+ self.conf.timeout,
67
+ )
68
+ parameters = {
69
+ 'host': self.conf.host,
70
+ 'port': self.conf.port,
71
+ 'username': self.conf.username,
72
+ 'password': self.conf.password,
73
+ 'ssl': self.conf.secure,
74
+ 'max_connections': self.conf.max,
75
+ 'socket_connect_timeout': self.conf.timeout,
76
+ 'socket_timeout': self.conf.timeout,
77
+ 'decode_responses': False,
78
+ }
79
+ connection = None
80
+ try:
81
+ if self.conf.type == ConnectionType.Cluster:
82
+ connection = RedisCluster(**parameters)
83
+ else:
84
+ connection = Redis(
85
+ db=self.conf.database,
86
+ **parameters,
87
+ )
88
+ connection.ping()
89
+ except Exception as error:
90
+ self.logger.error(
91
+ 'Redis connection or PING failed: type=%s error_type=%s',
92
+ connection_type,
93
+ type(error).__name__,
94
+ )
95
+ if connection is not None:
96
+ connection.close()
97
+ raise
98
+ self.connection = connection
99
+ self.logger.info(
100
+ 'Redis connection verified with PING: type=%s',
101
+ connection_type,
102
+ )
103
+
104
+ def connected(self) -> Union[Redis, RedisCluster]:
105
+ if self.connection is None:
106
+ raise RuntimeError('Connect the Redis broker before use')
107
+ return self.connection
108
+
109
+ def declare(
110
+ self,
111
+ parameters: Any,
112
+ ) -> None:
113
+ subscription = Subscription.parse(parameters)
114
+ self.logger.debug(
115
+ 'Delegating stream declaration: queue=%s group=%s',
116
+ subscription.queue,
117
+ subscription.group,
118
+ )
119
+ self.create_queue(
120
+ subscription.queue,
121
+ group=subscription.group,
122
+ )
123
+
124
+ def emit(
125
+ self,
126
+ body: Any,
127
+ parameters: Any,
128
+ ) -> str:
129
+ subscription = Subscription.parse(parameters)
130
+ self.logger.debug(
131
+ 'Delegating message publication: queue=%s group=%s',
132
+ subscription.queue,
133
+ subscription.group,
134
+ )
135
+ return self.queue(
136
+ subscription.queue,
137
+ group=subscription.group,
138
+ ).send(body)
139
+
140
+ def queue(
141
+ self,
142
+ name: str,
143
+ group: str = 'DefStream',
144
+ ) -> Queue:
145
+ self.logger.debug(
146
+ 'Creating Queue: queue=%s group=%s',
147
+ name,
148
+ group,
149
+ )
150
+ return Queue(
151
+ self.connected(),
152
+ self.conf.encode,
153
+ self.conf.decode,
154
+ name,
155
+ group,
156
+ )
157
+
158
+ def stream(
159
+ self,
160
+ name: str,
161
+ group: str = 'DefStream',
162
+ ) -> Queue:
163
+ return self.queue(
164
+ name,
165
+ group,
166
+ )
167
+
168
+ def consumer(
169
+ self,
170
+ handler: Optional[EventHandler] = None,
171
+ qos: int = 1,
172
+ reclaim_idle: Optional[int] = 60000,
173
+ ) -> Consumer:
174
+ self.logger.debug(
175
+ 'Creating Consumer: qos=%s reclaim_idle=%sms',
176
+ qos,
177
+ reclaim_idle,
178
+ )
179
+ return Consumer(
180
+ self.connected(),
181
+ self.conf.encode,
182
+ self.conf.decode,
183
+ handler,
184
+ qos,
185
+ reclaim_idle,
186
+ )
187
+
188
+ def create_queue(
189
+ self,
190
+ name: str,
191
+ group: str = 'DefStream',
192
+ ) -> None:
193
+ self.logger.debug(
194
+ 'Delegating Consumer Group creation: queue=%s group=%s',
195
+ name,
196
+ group,
197
+ )
198
+ self.queue(
199
+ name,
200
+ group,
201
+ ).create_group()
202
+
203
+ def delete_queue(
204
+ self,
205
+ name: Union[str, Queue],
206
+ ) -> int:
207
+ queue = str(name)
208
+ self.logger.debug(
209
+ 'Deleting Redis stream: queue=%s',
210
+ queue,
211
+ )
212
+ try:
213
+ deleted = self.connected().delete(queue)
214
+ except Exception as error:
215
+ self.logger.error(
216
+ 'Redis stream deletion failed: queue=%s error_type=%s',
217
+ queue,
218
+ type(error).__name__,
219
+ )
220
+ raise
221
+ if isinstance(deleted, int) and deleted > 0:
222
+ self.logger.info(
223
+ 'Redis stream deletion completed: queue=%s deleted=%s',
224
+ queue,
225
+ deleted,
226
+ )
227
+ else:
228
+ self.logger.info(
229
+ 'No Redis stream to delete: queue=%s deleted=%s',
230
+ queue,
231
+ deleted,
232
+ )
233
+ return deleted
234
+
235
+ def create_stream(
236
+ self,
237
+ name: str,
238
+ group: str = 'DefStream',
239
+ ) -> None:
240
+ self.create_queue(
241
+ name,
242
+ group,
243
+ )
244
+
245
+ def delete_stream(
246
+ self,
247
+ name: Union[str, Queue],
248
+ ) -> int:
249
+ return self.delete_queue(name)
250
+
251
+ def close(self) -> None:
252
+ if self.connection is not None:
253
+ self.logger.info('Closing Redis connection')
254
+ try:
255
+ self.connection.close()
256
+ except Exception as error:
257
+ self.logger.error(
258
+ 'Redis connection close failed: error_type=%s',
259
+ type(error).__name__,
260
+ )
261
+ raise
262
+ self.connection = None
263
+ self.logger.info('Redis connection closed')
264
+ else:
265
+ self.logger.debug('No Redis connection to close')