weenspace-queue 0.1.0__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.
- weenspace_queue/__init__.py +128 -0
- weenspace_queue/asyncio/__init__.py +4 -0
- weenspace_queue/asyncio/aws_async.py +38 -0
- weenspace_queue/asyncio/rabbitmq_async.py +182 -0
- weenspace_queue/base.py +103 -0
- weenspace_queue/constants.py +75 -0
- weenspace_queue/providers/__init__.py +4 -0
- weenspace_queue/providers/aws.py +273 -0
- weenspace_queue/providers/rabbitmq.py +205 -0
- weenspace_queue/utils.py +271 -0
- weenspace_queue-0.1.0.dist-info/METADATA +161 -0
- weenspace_queue-0.1.0.dist-info/RECORD +14 -0
- weenspace_queue-0.1.0.dist-info/WHEEL +4 -0
- weenspace_queue-0.1.0.dist-info/licenses/LICENSE +21 -0
weenspace_queue/utils.py
ADDED
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from typing import Any, Dict, List, Optional
|
|
5
|
+
|
|
6
|
+
from .constants import (
|
|
7
|
+
AWS_NUMBER_DATA_TYPE,
|
|
8
|
+
AWS_STRING_DATA_TYPE,
|
|
9
|
+
RABBITMQ_EXCHANGE_PREFIX,
|
|
10
|
+
RABBITMQ_QUEUE_PREFIX,
|
|
11
|
+
ROUTING_KEY_ATTR,
|
|
12
|
+
ROUTING_KEY_INDEX_PREFIX,
|
|
13
|
+
ROUTING_KEY_LENGTH_ATTR,
|
|
14
|
+
ROUTING_KEY_REVERSE_INDEX_PREFIX,
|
|
15
|
+
ROUTING_SEPARATOR,
|
|
16
|
+
SNS_ARN_MARKER,
|
|
17
|
+
SNS_NOTIFICATION_TYPE,
|
|
18
|
+
SQS_ARN_MARKER,
|
|
19
|
+
SQS_BODY_KEY,
|
|
20
|
+
SQS_FIFO_SUFFIX,
|
|
21
|
+
SQS_URL_MARKER,
|
|
22
|
+
WILDCARD_MULTI,
|
|
23
|
+
WILDCARD_SINGLE,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def encode_body(body: Any) -> bytes:
|
|
28
|
+
if isinstance(body, bytes):
|
|
29
|
+
return body
|
|
30
|
+
if body is None:
|
|
31
|
+
return b""
|
|
32
|
+
if isinstance(body, str):
|
|
33
|
+
return body.encode("utf-8")
|
|
34
|
+
return json.dumps(body).encode("utf-8")
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def decode_body(body: Any) -> str:
|
|
38
|
+
if isinstance(body, bytes):
|
|
39
|
+
return body.decode("utf-8")
|
|
40
|
+
return str(body)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def split_routing(value: str) -> List[str]:
|
|
44
|
+
if value == "":
|
|
45
|
+
return []
|
|
46
|
+
return value.split(ROUTING_SEPARATOR)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def is_sns_destination(destination: str) -> bool:
|
|
50
|
+
return SNS_ARN_MARKER in destination
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def is_sqs_destination(destination: str) -> bool:
|
|
54
|
+
return SQS_ARN_MARKER in destination or SQS_URL_MARKER in destination
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def is_fifo_queue(name_or_url: str) -> bool:
|
|
58
|
+
return name_or_url.rstrip("/").endswith(SQS_FIFO_SUFFIX)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def rabbitmq_queue_address(queue_id: str) -> str:
|
|
62
|
+
if queue_id.startswith(RABBITMQ_QUEUE_PREFIX) or queue_id.startswith(
|
|
63
|
+
RABBITMQ_EXCHANGE_PREFIX
|
|
64
|
+
):
|
|
65
|
+
return queue_id
|
|
66
|
+
return f"{RABBITMQ_QUEUE_PREFIX}{queue_id.lstrip('/')}"
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def rabbitmq_exchange_address(topic_id: str, routing_key: str = "") -> str:
|
|
70
|
+
if topic_id.startswith(RABBITMQ_EXCHANGE_PREFIX) or topic_id.startswith(
|
|
71
|
+
RABBITMQ_QUEUE_PREFIX
|
|
72
|
+
):
|
|
73
|
+
if routing_key and topic_id.count("/") == 2:
|
|
74
|
+
return f"{topic_id}/{routing_key}"
|
|
75
|
+
return topic_id
|
|
76
|
+
if routing_key:
|
|
77
|
+
return f"{RABBITMQ_EXCHANGE_PREFIX}{topic_id}/{routing_key}"
|
|
78
|
+
return f"{RABBITMQ_EXCHANGE_PREFIX}{topic_id}"
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def rabbitmq_resource_name(resource_id: str) -> str:
|
|
82
|
+
if resource_id.startswith(RABBITMQ_QUEUE_PREFIX):
|
|
83
|
+
return resource_id[len(RABBITMQ_QUEUE_PREFIX) :]
|
|
84
|
+
if resource_id.startswith(RABBITMQ_EXCHANGE_PREFIX):
|
|
85
|
+
remainder = resource_id[len(RABBITMQ_EXCHANGE_PREFIX) :]
|
|
86
|
+
return remainder.split("/", 1)[0]
|
|
87
|
+
return resource_id
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def inject_aws_routing_attrs(routing_key: str) -> Dict[str, Any]:
|
|
91
|
+
"""Hydrate SNS/SQS MessageAttributes so wildcard filter policies can match."""
|
|
92
|
+
if not routing_key:
|
|
93
|
+
return {}
|
|
94
|
+
segments = split_routing(routing_key)
|
|
95
|
+
attributes: Dict[str, Any] = {
|
|
96
|
+
ROUTING_KEY_ATTR: {
|
|
97
|
+
"DataType": AWS_STRING_DATA_TYPE,
|
|
98
|
+
"StringValue": routing_key,
|
|
99
|
+
},
|
|
100
|
+
ROUTING_KEY_LENGTH_ATTR: {
|
|
101
|
+
"DataType": AWS_NUMBER_DATA_TYPE,
|
|
102
|
+
"StringValue": str(len(segments)),
|
|
103
|
+
},
|
|
104
|
+
}
|
|
105
|
+
for idx, value in enumerate(segments):
|
|
106
|
+
attributes[f"{ROUTING_KEY_INDEX_PREFIX}{idx}"] = {
|
|
107
|
+
"DataType": AWS_STRING_DATA_TYPE,
|
|
108
|
+
"StringValue": value,
|
|
109
|
+
}
|
|
110
|
+
attributes[f"{ROUTING_KEY_REVERSE_INDEX_PREFIX}{idx}"] = {
|
|
111
|
+
"DataType": AWS_STRING_DATA_TYPE,
|
|
112
|
+
"StringValue": segments[-(idx + 1)],
|
|
113
|
+
}
|
|
114
|
+
return attributes
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def rmq_pattern_to_aws_sns(routing_pattern: str) -> Dict[str, Any]:
|
|
118
|
+
"""
|
|
119
|
+
Translate RabbitMQ topic patterns into SNS Message Attribute filter policies.
|
|
120
|
+
|
|
121
|
+
Supported shapes:
|
|
122
|
+
- exact: order.placed
|
|
123
|
+
- single-word *: order.*.completed
|
|
124
|
+
- multi-word trailing #: global.orders.#
|
|
125
|
+
- multi-word leading #: #.delayed
|
|
126
|
+
- combined: eu.*.truck.#
|
|
127
|
+
- middle #: region.#.failed
|
|
128
|
+
"""
|
|
129
|
+
pattern = routing_pattern.strip()
|
|
130
|
+
if pattern in ("", WILDCARD_MULTI):
|
|
131
|
+
return {}
|
|
132
|
+
|
|
133
|
+
segments = split_routing(pattern)
|
|
134
|
+
if WILDCARD_SINGLE not in segments and WILDCARD_MULTI not in segments:
|
|
135
|
+
return {
|
|
136
|
+
ROUTING_KEY_ATTR: [pattern],
|
|
137
|
+
ROUTING_KEY_LENGTH_ATTR: [{"numeric": ["=", len(segments)]}],
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
hash_indexes = [i for i, segment in enumerate(segments) if segment == WILDCARD_MULTI]
|
|
141
|
+
policy: Dict[str, Any] = {}
|
|
142
|
+
|
|
143
|
+
if not hash_indexes:
|
|
144
|
+
policy[ROUTING_KEY_LENGTH_ATTR] = [{"numeric": ["=", len(segments)]}]
|
|
145
|
+
_apply_segment_filters(policy, segments, reverse=False)
|
|
146
|
+
return policy
|
|
147
|
+
|
|
148
|
+
first_hash = hash_indexes[0]
|
|
149
|
+
last_hash = hash_indexes[-1]
|
|
150
|
+
prefix = segments[:first_hash]
|
|
151
|
+
suffix = segments[last_hash + 1 :]
|
|
152
|
+
|
|
153
|
+
_apply_segment_filters(policy, prefix, reverse=False)
|
|
154
|
+
_apply_segment_filters(policy, list(reversed(suffix)), reverse=True)
|
|
155
|
+
return policy
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def _apply_segment_filters(
|
|
159
|
+
policy: Dict[str, Any], segments: List[str], reverse: bool
|
|
160
|
+
) -> None:
|
|
161
|
+
prefix = ROUTING_KEY_REVERSE_INDEX_PREFIX if reverse else ROUTING_KEY_INDEX_PREFIX
|
|
162
|
+
for idx, segment in enumerate(segments):
|
|
163
|
+
key = f"{prefix}{idx}"
|
|
164
|
+
if segment == WILDCARD_MULTI:
|
|
165
|
+
continue
|
|
166
|
+
if segment == WILDCARD_SINGLE:
|
|
167
|
+
policy[key] = [{"exists": True}]
|
|
168
|
+
else:
|
|
169
|
+
policy[key] = [segment]
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def rabbitmq_pattern_matches(pattern: str, routing_key: str) -> bool:
|
|
173
|
+
"""Reference matcher for RabbitMQ topic semantics used in tests."""
|
|
174
|
+
return _match_segments(split_routing(pattern), split_routing(routing_key))
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def _match_segments(pattern: List[str], key: List[str]) -> bool:
|
|
178
|
+
if not pattern:
|
|
179
|
+
return not key
|
|
180
|
+
head, tail = pattern[0], pattern[1:]
|
|
181
|
+
if head == WILDCARD_MULTI:
|
|
182
|
+
for consumed in range(len(key) + 1):
|
|
183
|
+
if _match_segments(tail, key[consumed:]):
|
|
184
|
+
return True
|
|
185
|
+
return False
|
|
186
|
+
if not key:
|
|
187
|
+
return False
|
|
188
|
+
if head in (WILDCARD_SINGLE, key[0]):
|
|
189
|
+
return _match_segments(tail, key[1:])
|
|
190
|
+
return False
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def sns_filter_matches(policy: Dict[str, Any], routing_key: str) -> bool:
|
|
194
|
+
"""Evaluate a translated SNS filter against injected routing attributes."""
|
|
195
|
+
if not policy:
|
|
196
|
+
return True
|
|
197
|
+
attributes = inject_aws_routing_attrs(routing_key)
|
|
198
|
+
for attr_name, conditions in policy.items():
|
|
199
|
+
actual = attributes.get(attr_name)
|
|
200
|
+
if actual is None:
|
|
201
|
+
return False
|
|
202
|
+
actual_value = actual["StringValue"]
|
|
203
|
+
if not _condition_matches(conditions, actual_value):
|
|
204
|
+
return False
|
|
205
|
+
return True
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def _condition_matches(conditions: List[Any], actual_value: str) -> bool:
|
|
209
|
+
for condition in conditions:
|
|
210
|
+
if isinstance(condition, dict):
|
|
211
|
+
if "exists" in condition:
|
|
212
|
+
if condition["exists"]:
|
|
213
|
+
return True
|
|
214
|
+
continue
|
|
215
|
+
numeric = condition.get("numeric")
|
|
216
|
+
if numeric and numeric[0] == "=":
|
|
217
|
+
return str(actual_value) == str(numeric[1])
|
|
218
|
+
continue
|
|
219
|
+
if str(condition) == str(actual_value):
|
|
220
|
+
return True
|
|
221
|
+
return False
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def unwrap_sqs_body(raw_body: str) -> tuple[bytes, str, Dict[str, Any]]:
|
|
225
|
+
"""Unwrap SNS-to-SQS envelopes when present; otherwise treat as the payload."""
|
|
226
|
+
attributes: Dict[str, Any] = {}
|
|
227
|
+
routing_key = ""
|
|
228
|
+
body = raw_body
|
|
229
|
+
try:
|
|
230
|
+
parsed = json.loads(raw_body)
|
|
231
|
+
except (TypeError, json.JSONDecodeError):
|
|
232
|
+
return encode_body(raw_body), routing_key, attributes
|
|
233
|
+
|
|
234
|
+
if isinstance(parsed, dict) and parsed.get("Type") == SNS_NOTIFICATION_TYPE:
|
|
235
|
+
body = parsed.get("Message", "")
|
|
236
|
+
sns_attrs = parsed.get("MessageAttributes") or {}
|
|
237
|
+
routing_key = _routing_key_from_sns_attrs(sns_attrs)
|
|
238
|
+
attributes["sns"] = {key: value for key, value in parsed.items() if key != "Message"}
|
|
239
|
+
return encode_body(body), routing_key, attributes
|
|
240
|
+
|
|
241
|
+
return encode_body(raw_body), routing_key, attributes
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def _routing_key_from_sns_attrs(sns_attrs: Dict[str, Any]) -> str:
|
|
245
|
+
routing = sns_attrs.get(ROUTING_KEY_ATTR) or {}
|
|
246
|
+
return str(routing.get("Value") or routing.get("StringValue") or "")
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def routing_key_from_sqs_attributes(message: Dict[str, Any]) -> str:
|
|
250
|
+
attrs = message.get("MessageAttributes") or {}
|
|
251
|
+
routing = attrs.get(ROUTING_KEY_ATTR) or {}
|
|
252
|
+
return str(routing.get("StringValue") or routing.get("Value") or "")
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def extract_sqs_body(message: Dict[str, Any]) -> tuple[bytes, str, Dict[str, Any]]:
|
|
256
|
+
raw = message.get(SQS_BODY_KEY, "")
|
|
257
|
+
body, routing_key, extra = unwrap_sqs_body(raw)
|
|
258
|
+
if not routing_key:
|
|
259
|
+
routing_key = routing_key_from_sqs_attributes(message)
|
|
260
|
+
extra["sqs"] = {
|
|
261
|
+
key: value
|
|
262
|
+
for key, value in message.items()
|
|
263
|
+
if key not in {SQS_BODY_KEY}
|
|
264
|
+
}
|
|
265
|
+
return body, routing_key, extra
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def optional_queue_name_from_url(queue_url: str) -> Optional[str]:
|
|
269
|
+
if not queue_url:
|
|
270
|
+
return None
|
|
271
|
+
return queue_url.rstrip("/").split("/")[-1]
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: weenspace-queue
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: WeenSpace queue client for RabbitMQ and AWS SQS, supporting both synchronous and asynchronous operations.
|
|
5
|
+
License: MIT
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Keywords: rabbitmq,amqp,messaging,event-driven,microservices,queue,weenspace
|
|
8
|
+
Author: WeenSpace
|
|
9
|
+
Author-email: weenspace@gmail.com
|
|
10
|
+
Requires-Python: >=3.14,<4.0
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
16
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
17
|
+
Classifier: Topic :: System :: Networking
|
|
18
|
+
Requires-Dist: packaging (>=25.0,<26.0)
|
|
19
|
+
Requires-Dist: python-qpid-proton (>=0.40.0,<0.41.0)
|
|
20
|
+
Requires-Dist: typing-extensions (>=4.15.0,<5.0.0)
|
|
21
|
+
Project-URL: Homepage, https://github.com/weenspace/weenspace-queue
|
|
22
|
+
Project-URL: Repository, https://github.com/weenspace/weenspace-queue
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
|
|
25
|
+
# WeenSpace RabbitMQ Client
|
|
26
|
+
|
|
27
|
+
[](https://pypi.org/project/weenspace-queue/)
|
|
28
|
+
[](https://www.python.org/downloads/)
|
|
29
|
+
[](LICENSE)
|
|
30
|
+
|
|
31
|
+
A powerful Python RabbitMQ client for AMQP 1.0 protocol, designed for event-driven microservices architecture.
|
|
32
|
+
|
|
33
|
+
## ✨ Features
|
|
34
|
+
|
|
35
|
+
- 🔐 **Authentication**: OAuth2, TLS/SSL, Basic Auth
|
|
36
|
+
- 📬 **Queue Types**: Classic, Quorum, Stream queues
|
|
37
|
+
- ☠️ **Dead Letter Queues**: Built-in DLQ support with configurable strategies
|
|
38
|
+
- ⚡ **Priority Queues**: Native priority queue support
|
|
39
|
+
- 🔄 **Auto Reconnection**: Configurable recovery with exponential backoff
|
|
40
|
+
- 🔀 **All Exchange Types**: Direct, Fanout, Topic, Headers
|
|
41
|
+
- 🚀 **Async Support**: Native asyncio integration
|
|
42
|
+
- 📊 **Streams**: RabbitMQ Streams with filtering support
|
|
43
|
+
|
|
44
|
+
## 📦 Installation
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
pip install weenspace-queue
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## 🚀 Quick Start
|
|
51
|
+
|
|
52
|
+
### Basic Publisher/Consumer
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
from weenspace_queue import (
|
|
56
|
+
Environment,
|
|
57
|
+
Connection,
|
|
58
|
+
Publisher,
|
|
59
|
+
Consumer,
|
|
60
|
+
Message,
|
|
61
|
+
ClassicQueueSpecification,
|
|
62
|
+
ExchangeSpecification,
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
# Create environment and connection
|
|
66
|
+
environment = Environment(uri="amqp://guest:guest@localhost:5672/")
|
|
67
|
+
connection = environment.connection()
|
|
68
|
+
|
|
69
|
+
# Declare queue with dead letter support
|
|
70
|
+
management = connection.management()
|
|
71
|
+
queue_spec = ClassicQueueSpecification(
|
|
72
|
+
name="my-queue",
|
|
73
|
+
is_durable=True,
|
|
74
|
+
dead_letter_exchange="dlx",
|
|
75
|
+
dead_letter_routing_key="dlx-key",
|
|
76
|
+
max_priority=10, # Enable priority
|
|
77
|
+
)
|
|
78
|
+
management.declare_queue(queue_spec)
|
|
79
|
+
|
|
80
|
+
# Publish message
|
|
81
|
+
publisher = connection.publisher("/queues/my-queue")
|
|
82
|
+
publisher.publish(Message(body=b"Hello WeenSpace!"))
|
|
83
|
+
|
|
84
|
+
# Consume messages
|
|
85
|
+
def on_message(message):
|
|
86
|
+
print(f"Received: {message.body}")
|
|
87
|
+
message.accept()
|
|
88
|
+
|
|
89
|
+
consumer = connection.consumer("/queues/my-queue", handler=on_message)
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Async Support
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
import asyncio
|
|
96
|
+
from weenspace_queue.asyncio import AsyncEnvironment
|
|
97
|
+
|
|
98
|
+
async def main():
|
|
99
|
+
async with AsyncEnvironment(uri="amqp://localhost:5672/") as env:
|
|
100
|
+
async with env.connection() as conn:
|
|
101
|
+
publisher = await conn.publisher("/queues/my-queue")
|
|
102
|
+
await publisher.publish(Message(body=b"Async message!"))
|
|
103
|
+
|
|
104
|
+
asyncio.run(main())
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### OAuth2 Authentication
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
from weenspace_queue import Environment, OAuth2Options
|
|
111
|
+
|
|
112
|
+
oauth = OAuth2Options(token="your_jwt_token")
|
|
113
|
+
environment = Environment(
|
|
114
|
+
uri="amqp://localhost:5672/",
|
|
115
|
+
oauth2_options=oauth
|
|
116
|
+
)
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### TLS/SSL Connection
|
|
120
|
+
|
|
121
|
+
```python
|
|
122
|
+
from weenspace_queue import Environment, SslConfiguration
|
|
123
|
+
|
|
124
|
+
ssl_config = SslConfiguration(
|
|
125
|
+
ca_cert="/path/to/ca.pem",
|
|
126
|
+
client_cert="/path/to/client.pem",
|
|
127
|
+
client_key="/path/to/client.key"
|
|
128
|
+
)
|
|
129
|
+
environment = Environment(
|
|
130
|
+
uri="amqps://localhost:5671/",
|
|
131
|
+
ssl_configuration=ssl_config
|
|
132
|
+
)
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## 📚 Documentation
|
|
136
|
+
|
|
137
|
+
See [examples](./examples) folder for more detailed usage examples.
|
|
138
|
+
|
|
139
|
+
## 🔄 Migration from python-rabbitmq
|
|
140
|
+
|
|
141
|
+
```python
|
|
142
|
+
# Old (python-rabbitmq)
|
|
143
|
+
# from python_rabbitmq import RabbitMQ
|
|
144
|
+
|
|
145
|
+
# New (weenspace-queue)
|
|
146
|
+
from weenspace_queue import Environment, Connection
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## 📋 Requirements
|
|
150
|
+
|
|
151
|
+
- Python 3.13+
|
|
152
|
+
- RabbitMQ 4.x with AMQP 1.0 plugin enabled
|
|
153
|
+
|
|
154
|
+
## 📄 License
|
|
155
|
+
|
|
156
|
+
MIT License - Based on the official [RabbitMQ AMQP Python Client](https://github.com/rabbitmq/rabbitmq-amqp-python-client)
|
|
157
|
+
|
|
158
|
+
## 🙏 Credits
|
|
159
|
+
|
|
160
|
+
This library is based on the official RabbitMQ AMQP 1.0 Python client by the RabbitMQ team.
|
|
161
|
+
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
weenspace_queue/__init__.py,sha256=RGWo5dl9u-FfSlxT-dYiKg-ZPFu7ZBs3gd-SYOvuTaI,3964
|
|
2
|
+
weenspace_queue/asyncio/__init__.py,sha256=DO2yDMXX-WosrZfxUbilQvUsIZlK-4I8K5qREu--4FI,139
|
|
3
|
+
weenspace_queue/asyncio/aws_async.py,sha256=Jd-6tpF5PnLdqxTQ_9ScLkPihRl7NrcWFIPX_d4Da-k,1356
|
|
4
|
+
weenspace_queue/asyncio/rabbitmq_async.py,sha256=-FWzSHLWV7f1wWebpYhRh2Wm7u5mhIw4elpZn7X8zRU,6339
|
|
5
|
+
weenspace_queue/base.py,sha256=XTnA4afHlxgDr6fgZKEaox33AEaOYSZt5zRvNDDkSnQ,2602
|
|
6
|
+
weenspace_queue/constants.py,sha256=RGpCUeu7GEv8yX7DGkBjxAqPoeIBMtrGzIRGWPau3kM,2009
|
|
7
|
+
weenspace_queue/providers/__init__.py,sha256=mnVEFyRt1ttlNxiRUAUUV2yZl3djJhsKVHCRIsMo4cw,107
|
|
8
|
+
weenspace_queue/providers/aws.py,sha256=0pQgcIciVbR2b0Zj4l4bTin_vCeUsh6eMN7RNLZJw50,10671
|
|
9
|
+
weenspace_queue/providers/rabbitmq.py,sha256=0tDBx-oxBOHgy1UefmeUL0VvmWJPv-HCrYDL7qIeZKE,7390
|
|
10
|
+
weenspace_queue/utils.py,sha256=6CB1kSNxuiGxA7A6dEDIooc7YHCw7cGuIpwZmq4mG74,8809
|
|
11
|
+
weenspace_queue-0.1.0.dist-info/METADATA,sha256=d-trZDroRkRXlVba_1e441U_Z12JqE9P2c1bu16UiRg,4618
|
|
12
|
+
weenspace_queue-0.1.0.dist-info/WHEEL,sha256=eY7nduwzv-ldUxpzbRlxwvC693Hg6PX8bWDjEHjZ_dk,88
|
|
13
|
+
weenspace_queue-0.1.0.dist-info/licenses/LICENSE,sha256=aHFdqhTZFeswtMH5Fi6Owota-zZ_juV_37U2wuj_axU,1066
|
|
14
|
+
weenspace_queue-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 WeenSpace
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|