weenspace-queue 0.1.0__tar.gz → 0.1.2__tar.gz
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-0.1.0 → weenspace_queue-0.1.2}/PKG-INFO +30 -53
- {weenspace_queue-0.1.0 → weenspace_queue-0.1.2}/README.md +23 -50
- {weenspace_queue-0.1.0 → weenspace_queue-0.1.2}/pyproject.toml +17 -16
- {weenspace_queue-0.1.0 → weenspace_queue-0.1.2}/weenspace_queue/__init__.py +64 -37
- weenspace_queue-0.1.2/weenspace_queue/asyncio/__init__.py +23 -0
- {weenspace_queue-0.1.0 → weenspace_queue-0.1.2}/weenspace_queue/asyncio/aws_async.py +15 -5
- {weenspace_queue-0.1.0 → weenspace_queue-0.1.2}/weenspace_queue/asyncio/rabbitmq_async.py +53 -24
- {weenspace_queue-0.1.0 → weenspace_queue-0.1.2}/weenspace_queue/base.py +17 -4
- weenspace_queue-0.1.2/weenspace_queue/providers/__init__.py +15 -0
- {weenspace_queue-0.1.0 → weenspace_queue-0.1.2}/weenspace_queue/providers/aws.py +19 -6
- {weenspace_queue-0.1.0 → weenspace_queue-0.1.2}/weenspace_queue/providers/rabbitmq.py +62 -32
- weenspace_queue-0.1.2/weenspace_queue/ssl_configuration.py +3 -0
- {weenspace_queue-0.1.0 → weenspace_queue-0.1.2}/weenspace_queue/utils.py +8 -0
- weenspace_queue-0.1.0/weenspace_queue/asyncio/__init__.py +0 -4
- weenspace_queue-0.1.0/weenspace_queue/providers/__init__.py +0 -4
- {weenspace_queue-0.1.0 → weenspace_queue-0.1.2}/LICENSE +0 -0
- {weenspace_queue-0.1.0 → weenspace_queue-0.1.2}/weenspace_queue/constants.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: weenspace-queue
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.2
|
|
4
4
|
Summary: WeenSpace queue client for RabbitMQ and AWS SQS, supporting both synchronous and asynchronous operations.
|
|
5
5
|
License: MIT
|
|
6
6
|
License-File: LICENSE
|
|
@@ -13,11 +13,15 @@ Classifier: Intended Audience :: Developers
|
|
|
13
13
|
Classifier: License :: OSI Approved :: MIT License
|
|
14
14
|
Classifier: Programming Language :: Python :: 3
|
|
15
15
|
Classifier: Programming Language :: Python :: 3.14
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.15
|
|
16
17
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
17
18
|
Classifier: Topic :: System :: Networking
|
|
18
|
-
|
|
19
|
+
Provides-Extra: aws
|
|
20
|
+
Requires-Dist: boto3 (>=1.43.100,<2.0.0) ; extra == "aws"
|
|
21
|
+
Requires-Dist: packaging (>=26.3,<27.0)
|
|
19
22
|
Requires-Dist: python-qpid-proton (>=0.40.0,<0.41.0)
|
|
20
|
-
Requires-Dist:
|
|
23
|
+
Requires-Dist: rabbitmq-amqp-python-client (>=1.0.1,<2.0.0)
|
|
24
|
+
Requires-Dist: typing-extensions (>=4.16.0,<5.0.0)
|
|
21
25
|
Project-URL: Homepage, https://github.com/weenspace/weenspace-queue
|
|
22
26
|
Project-URL: Repository, https://github.com/weenspace/weenspace-queue
|
|
23
27
|
Description-Content-Type: text/markdown
|
|
@@ -49,86 +53,59 @@ pip install weenspace-queue
|
|
|
49
53
|
|
|
50
54
|
## 🚀 Quick Start
|
|
51
55
|
|
|
52
|
-
###
|
|
56
|
+
### Unified Publisher/Consumer
|
|
53
57
|
|
|
54
58
|
```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)
|
|
59
|
+
from weenspace_queue import Message, QueueClient, QueueSpecification
|
|
79
60
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
61
|
+
client = QueueClient("rabbitmq", uri="amqp://guest:guest@localhost:5672/")
|
|
62
|
+
queue = client.declare_queue(QueueSpecification(name="my-queue"))
|
|
63
|
+
client.publish(queue, Message(body=b"Hello WeenSpace!"))
|
|
83
64
|
|
|
84
|
-
|
|
85
|
-
def on_message(message):
|
|
65
|
+
def on_message(message: Message) -> None:
|
|
86
66
|
print(f"Received: {message.body}")
|
|
87
67
|
message.accept()
|
|
88
68
|
|
|
89
|
-
|
|
69
|
+
client.consume(queue, handler=on_message, prefetch=10)
|
|
90
70
|
```
|
|
91
71
|
|
|
72
|
+
Install AWS support with `pip install "weenspace-queue[aws]"`.
|
|
73
|
+
|
|
92
74
|
### Async Support
|
|
93
75
|
|
|
94
76
|
```python
|
|
95
77
|
import asyncio
|
|
96
|
-
from weenspace_queue
|
|
78
|
+
from weenspace_queue import AsyncQueueClient, Message, QueueSpecification
|
|
97
79
|
|
|
98
80
|
async def main():
|
|
99
|
-
async with
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
await publisher.publish(Message(body=b"Async message!"))
|
|
81
|
+
async with AsyncQueueClient("rabbitmq", uri="amqp://localhost:5672/") as client:
|
|
82
|
+
queue = await client.declare_queue(QueueSpecification(name="my-queue"))
|
|
83
|
+
await client.publish(queue, Message(body=b"Async message!"))
|
|
103
84
|
|
|
104
85
|
asyncio.run(main())
|
|
105
86
|
```
|
|
106
87
|
|
|
107
|
-
###
|
|
88
|
+
### RabbitMQ authentication
|
|
108
89
|
|
|
109
90
|
```python
|
|
110
|
-
from weenspace_queue import
|
|
91
|
+
from weenspace_queue import QueueClient
|
|
111
92
|
|
|
112
|
-
|
|
113
|
-
|
|
93
|
+
client = QueueClient(
|
|
94
|
+
"rabbitmq",
|
|
114
95
|
uri="amqp://localhost:5672/",
|
|
115
|
-
oauth2_options=
|
|
96
|
+
oauth2_options=your_oauth_options
|
|
116
97
|
)
|
|
117
98
|
```
|
|
118
99
|
|
|
119
100
|
### TLS/SSL Connection
|
|
120
101
|
|
|
121
102
|
```python
|
|
122
|
-
from weenspace_queue import
|
|
103
|
+
from weenspace_queue import QueueClient
|
|
123
104
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
client_cert="/path/to/client.pem",
|
|
127
|
-
client_key="/path/to/client.key"
|
|
128
|
-
)
|
|
129
|
-
environment = Environment(
|
|
105
|
+
client = QueueClient(
|
|
106
|
+
"rabbitmq",
|
|
130
107
|
uri="amqps://localhost:5671/",
|
|
131
|
-
|
|
108
|
+
ssl_context=your_ssl_context
|
|
132
109
|
)
|
|
133
110
|
```
|
|
134
111
|
|
|
@@ -143,7 +120,7 @@ See [examples](./examples) folder for more detailed usage examples.
|
|
|
143
120
|
# from python_rabbitmq import RabbitMQ
|
|
144
121
|
|
|
145
122
|
# New (weenspace-queue)
|
|
146
|
-
from weenspace_queue import
|
|
123
|
+
from weenspace_queue import QueueClient
|
|
147
124
|
```
|
|
148
125
|
|
|
149
126
|
## 📋 Requirements
|
|
@@ -25,86 +25,59 @@ pip install weenspace-queue
|
|
|
25
25
|
|
|
26
26
|
## 🚀 Quick Start
|
|
27
27
|
|
|
28
|
-
###
|
|
28
|
+
### Unified Publisher/Consumer
|
|
29
29
|
|
|
30
30
|
```python
|
|
31
|
-
from weenspace_queue import
|
|
32
|
-
Environment,
|
|
33
|
-
Connection,
|
|
34
|
-
Publisher,
|
|
35
|
-
Consumer,
|
|
36
|
-
Message,
|
|
37
|
-
ClassicQueueSpecification,
|
|
38
|
-
ExchangeSpecification,
|
|
39
|
-
)
|
|
40
|
-
|
|
41
|
-
# Create environment and connection
|
|
42
|
-
environment = Environment(uri="amqp://guest:guest@localhost:5672/")
|
|
43
|
-
connection = environment.connection()
|
|
44
|
-
|
|
45
|
-
# Declare queue with dead letter support
|
|
46
|
-
management = connection.management()
|
|
47
|
-
queue_spec = ClassicQueueSpecification(
|
|
48
|
-
name="my-queue",
|
|
49
|
-
is_durable=True,
|
|
50
|
-
dead_letter_exchange="dlx",
|
|
51
|
-
dead_letter_routing_key="dlx-key",
|
|
52
|
-
max_priority=10, # Enable priority
|
|
53
|
-
)
|
|
54
|
-
management.declare_queue(queue_spec)
|
|
31
|
+
from weenspace_queue import Message, QueueClient, QueueSpecification
|
|
55
32
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
33
|
+
client = QueueClient("rabbitmq", uri="amqp://guest:guest@localhost:5672/")
|
|
34
|
+
queue = client.declare_queue(QueueSpecification(name="my-queue"))
|
|
35
|
+
client.publish(queue, Message(body=b"Hello WeenSpace!"))
|
|
59
36
|
|
|
60
|
-
|
|
61
|
-
def on_message(message):
|
|
37
|
+
def on_message(message: Message) -> None:
|
|
62
38
|
print(f"Received: {message.body}")
|
|
63
39
|
message.accept()
|
|
64
40
|
|
|
65
|
-
|
|
41
|
+
client.consume(queue, handler=on_message, prefetch=10)
|
|
66
42
|
```
|
|
67
43
|
|
|
44
|
+
Install AWS support with `pip install "weenspace-queue[aws]"`.
|
|
45
|
+
|
|
68
46
|
### Async Support
|
|
69
47
|
|
|
70
48
|
```python
|
|
71
49
|
import asyncio
|
|
72
|
-
from weenspace_queue
|
|
50
|
+
from weenspace_queue import AsyncQueueClient, Message, QueueSpecification
|
|
73
51
|
|
|
74
52
|
async def main():
|
|
75
|
-
async with
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
await publisher.publish(Message(body=b"Async message!"))
|
|
53
|
+
async with AsyncQueueClient("rabbitmq", uri="amqp://localhost:5672/") as client:
|
|
54
|
+
queue = await client.declare_queue(QueueSpecification(name="my-queue"))
|
|
55
|
+
await client.publish(queue, Message(body=b"Async message!"))
|
|
79
56
|
|
|
80
57
|
asyncio.run(main())
|
|
81
58
|
```
|
|
82
59
|
|
|
83
|
-
###
|
|
60
|
+
### RabbitMQ authentication
|
|
84
61
|
|
|
85
62
|
```python
|
|
86
|
-
from weenspace_queue import
|
|
63
|
+
from weenspace_queue import QueueClient
|
|
87
64
|
|
|
88
|
-
|
|
89
|
-
|
|
65
|
+
client = QueueClient(
|
|
66
|
+
"rabbitmq",
|
|
90
67
|
uri="amqp://localhost:5672/",
|
|
91
|
-
oauth2_options=
|
|
68
|
+
oauth2_options=your_oauth_options
|
|
92
69
|
)
|
|
93
70
|
```
|
|
94
71
|
|
|
95
72
|
### TLS/SSL Connection
|
|
96
73
|
|
|
97
74
|
```python
|
|
98
|
-
from weenspace_queue import
|
|
75
|
+
from weenspace_queue import QueueClient
|
|
99
76
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
client_cert="/path/to/client.pem",
|
|
103
|
-
client_key="/path/to/client.key"
|
|
104
|
-
)
|
|
105
|
-
environment = Environment(
|
|
77
|
+
client = QueueClient(
|
|
78
|
+
"rabbitmq",
|
|
106
79
|
uri="amqps://localhost:5671/",
|
|
107
|
-
|
|
80
|
+
ssl_context=your_ssl_context
|
|
108
81
|
)
|
|
109
82
|
```
|
|
110
83
|
|
|
@@ -119,7 +92,7 @@ See [examples](./examples) folder for more detailed usage examples.
|
|
|
119
92
|
# from python_rabbitmq import RabbitMQ
|
|
120
93
|
|
|
121
94
|
# New (weenspace-queue)
|
|
122
|
-
from weenspace_queue import
|
|
95
|
+
from weenspace_queue import QueueClient
|
|
123
96
|
```
|
|
124
97
|
|
|
125
98
|
## 📋 Requirements
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "weenspace-queue"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.2"
|
|
4
4
|
description = "WeenSpace queue client for RabbitMQ and AWS SQS, supporting both synchronous and asynchronous operations."
|
|
5
5
|
authors = ["WeenSpace <weenspace@gmail.com>"]
|
|
6
6
|
license = "MIT"
|
|
@@ -22,29 +22,30 @@ packages = [{include = "weenspace_queue"}]
|
|
|
22
22
|
[tool.poetry.dependencies]
|
|
23
23
|
python = "^3.14"
|
|
24
24
|
python-qpid-proton = "^0.40.0"
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
rabbitmq-amqp-python-client = "^1.0.1"
|
|
26
|
+
boto3 = { version = "^1.43.100", optional = true }
|
|
27
|
+
typing-extensions = "^4.16.0"
|
|
28
|
+
packaging = "^26.3"
|
|
29
|
+
|
|
30
|
+
[tool.poetry.extras]
|
|
31
|
+
aws = ["boto3"]
|
|
27
32
|
|
|
28
33
|
[tool.poetry.group.dev.dependencies]
|
|
29
|
-
|
|
34
|
+
boto3 = "^1.43.100"
|
|
35
|
+
pyjwt = "^2.14.0"
|
|
30
36
|
pika = "^1.4.4"
|
|
31
|
-
flake8 = "^7.
|
|
32
|
-
isort = "^
|
|
33
|
-
mypy = "^
|
|
34
|
-
pytest = "^9.
|
|
37
|
+
flake8 = "^7.4.1"
|
|
38
|
+
isort = "^9.0.1"
|
|
39
|
+
mypy = "^2.3.1"
|
|
40
|
+
pytest = "^9.1.1"
|
|
35
41
|
black = "^26.5.1"
|
|
36
|
-
requests = "^2.
|
|
37
|
-
pytest-asyncio = "^1.
|
|
42
|
+
requests = "^2.34.2"
|
|
43
|
+
pytest-asyncio = "^1.4.0"
|
|
38
44
|
|
|
39
45
|
[build-system]
|
|
40
|
-
requires = ["poetry-core"]
|
|
46
|
+
requires = ["poetry-core>=2.5.0"]
|
|
41
47
|
build-backend = "poetry.core.masonry.api"
|
|
42
48
|
|
|
43
|
-
[dependency-groups]
|
|
44
|
-
dev = [
|
|
45
|
-
"boto3>=1.43.80",
|
|
46
|
-
]
|
|
47
|
-
|
|
48
49
|
[tool.pytest.ini_options]
|
|
49
50
|
asyncio_mode = "auto"
|
|
50
51
|
|
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
from typing import Any, Callable,
|
|
2
|
-
|
|
3
|
-
from .asyncio.aws_async import AwsAsyncEngine
|
|
4
|
-
from .asyncio.rabbitmq_async import RabbitMqAsyncEngine
|
|
1
|
+
from typing import Any, Callable, Type
|
|
5
2
|
from .base import (
|
|
6
3
|
AsyncQueueEngine,
|
|
7
4
|
Message,
|
|
@@ -16,26 +13,54 @@ from .constants import (
|
|
|
16
13
|
Provider,
|
|
17
14
|
QueueKind,
|
|
18
15
|
)
|
|
19
|
-
|
|
20
|
-
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def __getattr__(name: str) -> Any:
|
|
19
|
+
"""Lazily expose the low-level RabbitMQ compatibility API."""
|
|
20
|
+
try:
|
|
21
|
+
from rabbitmq_amqp_python_client import __dict__ as rabbitmq_namespace
|
|
22
|
+
|
|
23
|
+
value = rabbitmq_namespace.get(name)
|
|
24
|
+
if value is None and name == "DirectReplyToConsumerOptions":
|
|
25
|
+
value = rabbitmq_namespace["ConsumerOptions"]
|
|
26
|
+
if value is None:
|
|
27
|
+
raise KeyError(name)
|
|
28
|
+
except (ImportError, KeyError) as exc:
|
|
29
|
+
raise AttributeError(name) from exc
|
|
30
|
+
globals()[name] = value
|
|
31
|
+
return value
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _engine_class(provider: str) -> Type[QueueEngine]:
|
|
35
|
+
if provider == PROVIDER_AWS:
|
|
36
|
+
from .providers.aws import AwsEngine
|
|
37
|
+
|
|
38
|
+
return AwsEngine
|
|
39
|
+
if provider == PROVIDER_RABBITMQ:
|
|
40
|
+
from .providers.rabbitmq import RabbitMqEngine
|
|
41
|
+
|
|
42
|
+
return RabbitMqEngine
|
|
43
|
+
raise ValueError(f"Unsupported provider '{provider}'")
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _async_engine_class(provider: str) -> Type[AsyncQueueEngine]:
|
|
47
|
+
if provider == PROVIDER_AWS:
|
|
48
|
+
from .asyncio.aws_async import AwsAsyncEngine
|
|
49
|
+
|
|
50
|
+
return AwsAsyncEngine
|
|
51
|
+
if provider == PROVIDER_RABBITMQ:
|
|
52
|
+
from .asyncio.rabbitmq_async import RabbitMqAsyncEngine
|
|
53
|
+
|
|
54
|
+
return RabbitMqAsyncEngine
|
|
55
|
+
raise ValueError(f"Unsupported provider '{provider}'")
|
|
21
56
|
|
|
22
57
|
|
|
23
58
|
class QueueClient:
|
|
24
59
|
"""Single client: pass provider name, then use the same publish/consume/topology methods."""
|
|
25
60
|
|
|
26
|
-
_ENGINES: Dict[str, Type[QueueEngine]] = {
|
|
27
|
-
PROVIDER_AWS: AwsEngine,
|
|
28
|
-
PROVIDER_RABBITMQ: RabbitMqEngine,
|
|
29
|
-
}
|
|
30
|
-
|
|
31
61
|
def __init__(self, provider: str, **config: Any) -> None:
|
|
32
62
|
prov_key = provider.lower().strip()
|
|
33
|
-
engine_cls =
|
|
34
|
-
if engine_cls is None:
|
|
35
|
-
supported = ", ".join(sorted(self._ENGINES))
|
|
36
|
-
raise ValueError(
|
|
37
|
-
f"Unsupported provider '{provider}'. Supported: {supported}"
|
|
38
|
-
)
|
|
63
|
+
engine_cls = _engine_class(prov_key)
|
|
39
64
|
self.provider = prov_key
|
|
40
65
|
self.engine: QueueEngine = engine_cls(**config)
|
|
41
66
|
|
|
@@ -48,11 +73,17 @@ class QueueClient:
|
|
|
48
73
|
def bind_pattern(self, queue_id: str, topic_id: str, pattern: str) -> None:
|
|
49
74
|
self.engine.bind_pattern(queue_id, topic_id, pattern)
|
|
50
75
|
|
|
51
|
-
def publish(self, destination: str, message: Message) ->
|
|
52
|
-
self.engine.publish(destination, message)
|
|
76
|
+
def publish(self, destination: str, message: Message) -> Any:
|
|
77
|
+
return self.engine.publish(destination, message)
|
|
53
78
|
|
|
54
|
-
def consume(
|
|
55
|
-
self
|
|
79
|
+
def consume(
|
|
80
|
+
self,
|
|
81
|
+
queue_id: str,
|
|
82
|
+
handler: Callable[[Message], None],
|
|
83
|
+
*,
|
|
84
|
+
prefetch: int | None = None,
|
|
85
|
+
) -> None:
|
|
86
|
+
return self.engine.consume(queue_id, handler, prefetch=prefetch)
|
|
56
87
|
|
|
57
88
|
def stop(self) -> None:
|
|
58
89
|
self.engine.stop()
|
|
@@ -70,19 +101,9 @@ class QueueClient:
|
|
|
70
101
|
class AsyncQueueClient:
|
|
71
102
|
"""Async twin of QueueClient. Same method names, same provider argument."""
|
|
72
103
|
|
|
73
|
-
_ENGINES: Dict[str, Type[AsyncQueueEngine]] = {
|
|
74
|
-
PROVIDER_AWS: AwsAsyncEngine,
|
|
75
|
-
PROVIDER_RABBITMQ: RabbitMqAsyncEngine,
|
|
76
|
-
}
|
|
77
|
-
|
|
78
104
|
def __init__(self, provider: str, **config: Any) -> None:
|
|
79
105
|
prov_key = provider.lower().strip()
|
|
80
|
-
engine_cls =
|
|
81
|
-
if engine_cls is None:
|
|
82
|
-
supported = ", ".join(sorted(self._ENGINES))
|
|
83
|
-
raise ValueError(
|
|
84
|
-
f"Unsupported provider '{provider}'. Supported: {supported}"
|
|
85
|
-
)
|
|
106
|
+
engine_cls = _async_engine_class(prov_key)
|
|
86
107
|
self.provider = prov_key
|
|
87
108
|
self.engine: AsyncQueueEngine = engine_cls(**config)
|
|
88
109
|
|
|
@@ -95,11 +116,17 @@ class AsyncQueueClient:
|
|
|
95
116
|
async def bind_pattern(self, queue_id: str, topic_id: str, pattern: str) -> None:
|
|
96
117
|
await self.engine.bind_pattern(queue_id, topic_id, pattern)
|
|
97
118
|
|
|
98
|
-
async def publish(self, destination: str, message: Message) ->
|
|
99
|
-
await self.engine.publish(destination, message)
|
|
100
|
-
|
|
101
|
-
async def consume(
|
|
102
|
-
|
|
119
|
+
async def publish(self, destination: str, message: Message) -> Any:
|
|
120
|
+
return await self.engine.publish(destination, message)
|
|
121
|
+
|
|
122
|
+
async def consume(
|
|
123
|
+
self,
|
|
124
|
+
queue_id: str,
|
|
125
|
+
handler: Callable[[Message], None],
|
|
126
|
+
*,
|
|
127
|
+
prefetch: int | None = None,
|
|
128
|
+
) -> None:
|
|
129
|
+
await self.engine.consume(queue_id, handler, prefetch=prefetch)
|
|
103
130
|
|
|
104
131
|
async def stop(self) -> None:
|
|
105
132
|
await self.engine.stop()
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
__all__ = ["AwsAsyncEngine", "RabbitMqAsyncEngine"]
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def __getattr__(name: str) -> Any:
|
|
7
|
+
try:
|
|
8
|
+
from rabbitmq_amqp_python_client.asyncio import __dict__ as rabbitmq_namespace
|
|
9
|
+
|
|
10
|
+
value = rabbitmq_namespace[name]
|
|
11
|
+
globals()[name] = value
|
|
12
|
+
return value
|
|
13
|
+
except (ImportError, KeyError):
|
|
14
|
+
pass
|
|
15
|
+
if name == "AwsAsyncEngine":
|
|
16
|
+
from .aws_async import AwsAsyncEngine
|
|
17
|
+
|
|
18
|
+
return AwsAsyncEngine
|
|
19
|
+
if name == "RabbitMqAsyncEngine":
|
|
20
|
+
from .rabbitmq_async import RabbitMqAsyncEngine
|
|
21
|
+
|
|
22
|
+
return RabbitMqAsyncEngine
|
|
23
|
+
raise AttributeError(name)
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
|
+
from typing import Any, Callable
|
|
5
|
+
|
|
4
6
|
from weenspace_queue.base import (
|
|
5
7
|
AsyncQueueEngine,
|
|
6
8
|
Message,
|
|
@@ -25,11 +27,19 @@ class AwsAsyncEngine(AsyncQueueEngine):
|
|
|
25
27
|
async def bind_pattern(self, queue_id: str, topic_id: str, pattern: str) -> None:
|
|
26
28
|
await asyncio.to_thread(self._sync.bind_pattern, queue_id, topic_id, pattern)
|
|
27
29
|
|
|
28
|
-
async def publish(self, destination: str, message: Message) ->
|
|
29
|
-
await asyncio.to_thread(self._sync.publish, destination, message)
|
|
30
|
-
|
|
31
|
-
async def consume(
|
|
32
|
-
|
|
30
|
+
async def publish(self, destination: str, message: Message) -> Any:
|
|
31
|
+
return await asyncio.to_thread(self._sync.publish, destination, message)
|
|
32
|
+
|
|
33
|
+
async def consume(
|
|
34
|
+
self,
|
|
35
|
+
queue_id: str,
|
|
36
|
+
handler: Callable[[Message], None],
|
|
37
|
+
*,
|
|
38
|
+
prefetch: int | None = None,
|
|
39
|
+
) -> None:
|
|
40
|
+
await asyncio.to_thread(
|
|
41
|
+
self._sync.consume, queue_id, handler, prefetch=prefetch
|
|
42
|
+
)
|
|
33
43
|
|
|
34
44
|
async def stop(self) -> None:
|
|
35
45
|
self._sync.stop()
|
|
@@ -2,7 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from typing import Any, Callable, Optional
|
|
4
4
|
|
|
5
|
-
from
|
|
5
|
+
from rabbitmq_amqp_python_client import (
|
|
6
6
|
ClassicQueueSpecification,
|
|
7
7
|
ExchangeSpecification,
|
|
8
8
|
ExchangeToQueueBindingSpecification,
|
|
@@ -11,10 +11,10 @@ from weenspace_queue import (
|
|
|
11
11
|
QuorumQueueSpecification,
|
|
12
12
|
StreamSpecification,
|
|
13
13
|
)
|
|
14
|
-
from
|
|
15
|
-
from
|
|
16
|
-
from
|
|
17
|
-
from
|
|
14
|
+
from rabbitmq_amqp_python_client.asyncio import AsyncEnvironment
|
|
15
|
+
from rabbitmq_amqp_python_client.delivery_context import DeliveryContext
|
|
16
|
+
from rabbitmq_amqp_python_client.amqp_consumer_handler import AMQPMessagingHandler
|
|
17
|
+
from rabbitmq_amqp_python_client.qpid.proton._events import Event
|
|
18
18
|
|
|
19
19
|
from weenspace_queue.base import (
|
|
20
20
|
AsyncQueueEngine,
|
|
@@ -35,10 +35,10 @@ from weenspace_queue.utils import (
|
|
|
35
35
|
)
|
|
36
36
|
|
|
37
37
|
_EXCHANGE_KIND_MAP = {
|
|
38
|
-
ExchangeKind.DIRECT: ExchangeType.
|
|
39
|
-
ExchangeKind.TOPIC: ExchangeType.
|
|
40
|
-
ExchangeKind.FANOUT: ExchangeType.
|
|
41
|
-
ExchangeKind.HEADERS: ExchangeType.
|
|
38
|
+
ExchangeKind.DIRECT: ExchangeType.direct,
|
|
39
|
+
ExchangeKind.TOPIC: ExchangeType.topic,
|
|
40
|
+
ExchangeKind.FANOUT: ExchangeType.fanout,
|
|
41
|
+
ExchangeKind.HEADERS: ExchangeType.headers,
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
|
|
@@ -62,6 +62,9 @@ class _CallbackHandler(AMQPMessagingHandler):
|
|
|
62
62
|
def requeue(evt: Event = event) -> None:
|
|
63
63
|
context.requeue(evt)
|
|
64
64
|
|
|
65
|
+
def modified(evt: Event = event) -> None:
|
|
66
|
+
context.discard_with_annotations(evt, {})
|
|
67
|
+
|
|
65
68
|
self._handler(
|
|
66
69
|
Message(
|
|
67
70
|
body=encode_body(proton_msg.body),
|
|
@@ -70,6 +73,7 @@ class _CallbackHandler(AMQPMessagingHandler):
|
|
|
70
73
|
accept=accept,
|
|
71
74
|
reject=reject,
|
|
72
75
|
requeue=requeue,
|
|
76
|
+
modified=modified,
|
|
73
77
|
)
|
|
74
78
|
)
|
|
75
79
|
|
|
@@ -88,6 +92,7 @@ class RabbitMqAsyncEngine(AsyncQueueEngine):
|
|
|
88
92
|
self._conn: Any = None
|
|
89
93
|
self._mgmt: Any = None
|
|
90
94
|
self._consumer: Any = None
|
|
95
|
+
self._publishers: dict[str, Any] = {}
|
|
91
96
|
|
|
92
97
|
async def _ensure(self) -> None:
|
|
93
98
|
if self._conn is not None:
|
|
@@ -95,6 +100,20 @@ class RabbitMqAsyncEngine(AsyncQueueEngine):
|
|
|
95
100
|
self._conn = await self._env.connection()
|
|
96
101
|
await self._conn.dial()
|
|
97
102
|
self._mgmt = await self._conn.management()
|
|
103
|
+
self._fix_quorum_delivery_limit()
|
|
104
|
+
|
|
105
|
+
def _fix_quorum_delivery_limit(self) -> None:
|
|
106
|
+
management = getattr(self._mgmt, "_management", self._mgmt)
|
|
107
|
+
declare_queue = management._declare_queue
|
|
108
|
+
|
|
109
|
+
def declare_queue_with_rabbitmq_key(spec: Any) -> Any:
|
|
110
|
+
body = declare_queue(spec)
|
|
111
|
+
arguments = body.get("arguments", {})
|
|
112
|
+
if "x-deliver-limit" in arguments:
|
|
113
|
+
arguments["x-delivery-limit"] = arguments.pop("x-deliver-limit")
|
|
114
|
+
return body
|
|
115
|
+
|
|
116
|
+
management._declare_queue = declare_queue_with_rabbitmq_key
|
|
98
117
|
|
|
99
118
|
async def declare_queue(self, spec: QueueSpecification) -> str:
|
|
100
119
|
await self._ensure()
|
|
@@ -141,25 +160,32 @@ class RabbitMqAsyncEngine(AsyncQueueEngine):
|
|
|
141
160
|
)
|
|
142
161
|
)
|
|
143
162
|
|
|
144
|
-
async def publish(self, destination: str, message: Message) ->
|
|
163
|
+
async def publish(self, destination: str, message: Message) -> Any:
|
|
145
164
|
await self._ensure()
|
|
146
165
|
address = self._publish_address(destination, message.routing_key)
|
|
147
|
-
publisher =
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
async def consume(
|
|
166
|
+
publisher = self._publishers.get(address)
|
|
167
|
+
if publisher is None:
|
|
168
|
+
publisher = await self._conn.publisher(address)
|
|
169
|
+
self._publishers[address] = publisher
|
|
170
|
+
proton_msg = ProtonMessage(body=encode_body(message.body))
|
|
171
|
+
proton_msg.inferred = True
|
|
172
|
+
if message.routing_key:
|
|
173
|
+
proton_msg.subject = message.routing_key
|
|
174
|
+
return await publisher.publish(proton_msg)
|
|
175
|
+
|
|
176
|
+
async def consume(
|
|
177
|
+
self,
|
|
178
|
+
queue_id: str,
|
|
179
|
+
handler: Callable[[Message], None],
|
|
180
|
+
*,
|
|
181
|
+
prefetch: Optional[int] = None,
|
|
182
|
+
) -> None:
|
|
158
183
|
await self._ensure()
|
|
159
184
|
destination = rabbitmq_queue_address(queue_id)
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
185
|
+
consumer_kwargs: dict[str, Any] = {"message_handler": _CallbackHandler(handler)}
|
|
186
|
+
if prefetch is not None:
|
|
187
|
+
consumer_kwargs["credit"] = prefetch
|
|
188
|
+
self._consumer = await self._conn.consumer(destination, **consumer_kwargs)
|
|
163
189
|
await self._consumer.run()
|
|
164
190
|
|
|
165
191
|
async def stop(self) -> None:
|
|
@@ -169,6 +195,9 @@ class RabbitMqAsyncEngine(AsyncQueueEngine):
|
|
|
169
195
|
async def close(self) -> None:
|
|
170
196
|
await self.stop()
|
|
171
197
|
if self._conn is not None:
|
|
198
|
+
for publisher in self._publishers.values():
|
|
199
|
+
await publisher.close()
|
|
200
|
+
self._publishers.clear()
|
|
172
201
|
await self._conn.close()
|
|
173
202
|
await self._env.close()
|
|
174
203
|
|
|
@@ -17,6 +17,7 @@ class Message:
|
|
|
17
17
|
accept: Optional[Callable[[], None]] = None
|
|
18
18
|
reject: Optional[Callable[[], None]] = None
|
|
19
19
|
requeue: Optional[Callable[[], None]] = None
|
|
20
|
+
modified: Optional[Callable[[], None]] = None
|
|
20
21
|
|
|
21
22
|
|
|
22
23
|
@dataclass
|
|
@@ -55,11 +56,17 @@ class QueueEngine(ABC):
|
|
|
55
56
|
pass
|
|
56
57
|
|
|
57
58
|
@abstractmethod
|
|
58
|
-
def publish(self, destination: str, message: Message) ->
|
|
59
|
+
def publish(self, destination: str, message: Message) -> Any:
|
|
59
60
|
pass
|
|
60
61
|
|
|
61
62
|
@abstractmethod
|
|
62
|
-
def consume(
|
|
63
|
+
def consume(
|
|
64
|
+
self,
|
|
65
|
+
queue_id: str,
|
|
66
|
+
handler: Callable[[Message], None],
|
|
67
|
+
*,
|
|
68
|
+
prefetch: Optional[int] = None,
|
|
69
|
+
) -> None:
|
|
63
70
|
pass
|
|
64
71
|
|
|
65
72
|
@abstractmethod
|
|
@@ -87,11 +94,17 @@ class AsyncQueueEngine(ABC):
|
|
|
87
94
|
pass
|
|
88
95
|
|
|
89
96
|
@abstractmethod
|
|
90
|
-
async def publish(self, destination: str, message: Message) ->
|
|
97
|
+
async def publish(self, destination: str, message: Message) -> Any:
|
|
91
98
|
pass
|
|
92
99
|
|
|
93
100
|
@abstractmethod
|
|
94
|
-
async def consume(
|
|
101
|
+
async def consume(
|
|
102
|
+
self,
|
|
103
|
+
queue_id: str,
|
|
104
|
+
handler: Callable[[Message], None],
|
|
105
|
+
*,
|
|
106
|
+
prefetch: Optional[int] = None,
|
|
107
|
+
) -> None:
|
|
95
108
|
pass
|
|
96
109
|
|
|
97
110
|
@abstractmethod
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
__all__ = ["AwsEngine", "RabbitMqEngine"]
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def __getattr__(name: str) -> Any:
|
|
7
|
+
if name == "AwsEngine":
|
|
8
|
+
from .aws import AwsEngine
|
|
9
|
+
|
|
10
|
+
return AwsEngine
|
|
11
|
+
if name == "RabbitMqEngine":
|
|
12
|
+
from .rabbitmq import RabbitMqEngine
|
|
13
|
+
|
|
14
|
+
return RabbitMqEngine
|
|
15
|
+
raise AttributeError(name)
|
|
@@ -20,6 +20,7 @@ from weenspace_queue.constants import (
|
|
|
20
20
|
SQS_FIFO_SUFFIX,
|
|
21
21
|
SQS_POLICY_ATTRIBUTE,
|
|
22
22
|
SQS_QUEUE_ARN_ATTRIBUTE,
|
|
23
|
+
SQS_ARN_MARKER,
|
|
23
24
|
SQS_RECEIPT_HANDLE_ATTR,
|
|
24
25
|
SQS_REDRIVE_POLICY_ATTRIBUTE,
|
|
25
26
|
SQS_RETENTION_ATTRIBUTE,
|
|
@@ -118,7 +119,7 @@ class AwsEngine(QueueEngine):
|
|
|
118
119
|
self.sns.subscribe(**subscribe_kwargs)
|
|
119
120
|
self._allow_sns_to_sqs(queue_url, queue_arn, topic_id)
|
|
120
121
|
|
|
121
|
-
def publish(self, destination: str, message: Message) ->
|
|
122
|
+
def publish(self, destination: str, message: Message) -> Dict[str, Any]:
|
|
122
123
|
body_str = decode_body(message.body)
|
|
123
124
|
msg_attrs = dict(inject_aws_routing_attrs(message.routing_key))
|
|
124
125
|
extra_attrs = message.attributes or {}
|
|
@@ -143,8 +144,7 @@ class AwsEngine(QueueEngine):
|
|
|
143
144
|
dedup = extra_attrs.get("message_deduplication_id")
|
|
144
145
|
if dedup:
|
|
145
146
|
publish_kwargs["MessageDeduplicationId"] = str(dedup)
|
|
146
|
-
self.sns.publish(**publish_kwargs)
|
|
147
|
-
return
|
|
147
|
+
return self.sns.publish(**publish_kwargs)
|
|
148
148
|
|
|
149
149
|
queue_url = self._as_queue_url(destination)
|
|
150
150
|
send_kwargs: Dict[str, Any] = {
|
|
@@ -160,15 +160,22 @@ class AwsEngine(QueueEngine):
|
|
|
160
160
|
dedup = extra_attrs.get("message_deduplication_id")
|
|
161
161
|
if dedup:
|
|
162
162
|
send_kwargs["MessageDeduplicationId"] = str(dedup)
|
|
163
|
-
self.sqs.send_message(**send_kwargs)
|
|
163
|
+
return self.sqs.send_message(**send_kwargs)
|
|
164
164
|
|
|
165
|
-
def consume(
|
|
165
|
+
def consume(
|
|
166
|
+
self,
|
|
167
|
+
queue_id: str,
|
|
168
|
+
handler: Callable[[Message], None],
|
|
169
|
+
*,
|
|
170
|
+
prefetch: int | None = None,
|
|
171
|
+
) -> None:
|
|
166
172
|
queue_url = self._as_queue_url(queue_id)
|
|
167
173
|
self._running = True
|
|
174
|
+
max_messages = self._max_messages if prefetch is None else max(1, min(prefetch, 10))
|
|
168
175
|
while self._running:
|
|
169
176
|
response = self.sqs.receive_message(
|
|
170
177
|
QueueUrl=queue_url,
|
|
171
|
-
MaxNumberOfMessages=
|
|
178
|
+
MaxNumberOfMessages=max_messages,
|
|
172
179
|
WaitTimeSeconds=self._wait_time_seconds,
|
|
173
180
|
MessageAttributeNames=["All"],
|
|
174
181
|
AttributeNames=["All"],
|
|
@@ -203,6 +210,11 @@ class AwsEngine(QueueEngine):
|
|
|
203
210
|
QueueUrl=queue_url, ReceiptHandle=handle, VisibilityTimeout=0
|
|
204
211
|
)
|
|
205
212
|
|
|
213
|
+
def modified(handle: str = receipt) -> None:
|
|
214
|
+
self.sqs.change_message_visibility(
|
|
215
|
+
QueueUrl=queue_url, ReceiptHandle=handle, VisibilityTimeout=0
|
|
216
|
+
)
|
|
217
|
+
|
|
206
218
|
return Message(
|
|
207
219
|
body=body,
|
|
208
220
|
routing_key=routing_key,
|
|
@@ -210,6 +222,7 @@ class AwsEngine(QueueEngine):
|
|
|
210
222
|
accept=accept,
|
|
211
223
|
reject=reject,
|
|
212
224
|
requeue=requeue,
|
|
225
|
+
modified=modified,
|
|
213
226
|
)
|
|
214
227
|
|
|
215
228
|
def _as_queue_url(self, queue_id: str) -> str:
|
|
@@ -2,7 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from typing import Any, Callable, Optional
|
|
4
4
|
|
|
5
|
-
from
|
|
5
|
+
from rabbitmq_amqp_python_client import (
|
|
6
6
|
AMQPMessagingHandler,
|
|
7
7
|
ClassicQueueSpecification,
|
|
8
8
|
Environment,
|
|
@@ -14,7 +14,7 @@ from weenspace_queue import (
|
|
|
14
14
|
QuorumQueueSpecification,
|
|
15
15
|
StreamSpecification,
|
|
16
16
|
)
|
|
17
|
-
from
|
|
17
|
+
from rabbitmq_amqp_python_client.delivery_context import DeliveryContext
|
|
18
18
|
|
|
19
19
|
from weenspace_queue.base import Message, QueueEngine, QueueSpecification, TopicSpecification
|
|
20
20
|
from weenspace_queue.constants import (
|
|
@@ -30,10 +30,10 @@ from weenspace_queue.utils import (
|
|
|
30
30
|
)
|
|
31
31
|
|
|
32
32
|
_EXCHANGE_KIND_MAP = {
|
|
33
|
-
ExchangeKind.DIRECT: ExchangeType.
|
|
34
|
-
ExchangeKind.TOPIC: ExchangeType.
|
|
35
|
-
ExchangeKind.FANOUT: ExchangeType.
|
|
36
|
-
ExchangeKind.HEADERS: ExchangeType.
|
|
33
|
+
ExchangeKind.DIRECT: ExchangeType.direct,
|
|
34
|
+
ExchangeKind.TOPIC: ExchangeType.topic,
|
|
35
|
+
ExchangeKind.FANOUT: ExchangeType.fanout,
|
|
36
|
+
ExchangeKind.HEADERS: ExchangeType.headers,
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
|
|
@@ -64,6 +64,9 @@ class _CallbackHandler(AMQPMessagingHandler):
|
|
|
64
64
|
def requeue(evt: Event = event) -> None:
|
|
65
65
|
context.requeue(evt)
|
|
66
66
|
|
|
67
|
+
def modified(evt: Event = event) -> None:
|
|
68
|
+
context.discard_with_annotations(evt, {})
|
|
69
|
+
|
|
67
70
|
self._handler(
|
|
68
71
|
Message(
|
|
69
72
|
body=encode_body(proton_msg.body),
|
|
@@ -72,6 +75,7 @@ class _CallbackHandler(AMQPMessagingHandler):
|
|
|
72
75
|
accept=accept,
|
|
73
76
|
reject=reject,
|
|
74
77
|
requeue=requeue,
|
|
78
|
+
modified=modified,
|
|
75
79
|
)
|
|
76
80
|
)
|
|
77
81
|
|
|
@@ -90,7 +94,21 @@ class RabbitMqEngine(QueueEngine):
|
|
|
90
94
|
self._conn = self._env.connection()
|
|
91
95
|
self._conn.dial()
|
|
92
96
|
self._mgmt = self._conn.management()
|
|
97
|
+
self._fix_quorum_delivery_limit()
|
|
93
98
|
self._consumer: Optional[Any] = None
|
|
99
|
+
self._publishers: dict[str, Any] = {}
|
|
100
|
+
|
|
101
|
+
def _fix_quorum_delivery_limit(self) -> None:
|
|
102
|
+
declare_queue = self._mgmt._declare_queue
|
|
103
|
+
|
|
104
|
+
def declare_queue_with_rabbitmq_key(spec: Any) -> Any:
|
|
105
|
+
body = declare_queue(spec)
|
|
106
|
+
arguments = body.get("arguments", {})
|
|
107
|
+
if "x-deliver-limit" in arguments:
|
|
108
|
+
arguments["x-delivery-limit"] = arguments.pop("x-deliver-limit")
|
|
109
|
+
return body
|
|
110
|
+
|
|
111
|
+
self._mgmt._declare_queue = declare_queue_with_rabbitmq_key
|
|
94
112
|
|
|
95
113
|
def declare_queue(self, spec: QueueSpecification) -> str:
|
|
96
114
|
if spec.kind == QueueKind.STREAM:
|
|
@@ -156,35 +174,44 @@ class RabbitMqEngine(QueueEngine):
|
|
|
156
174
|
)
|
|
157
175
|
)
|
|
158
176
|
|
|
159
|
-
def publish(self, destination: str, message: Message) ->
|
|
177
|
+
def publish(self, destination: str, message: Message) -> Any:
|
|
160
178
|
address = self._publish_address(destination, message.routing_key)
|
|
161
|
-
publisher = self.
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
def consume(
|
|
179
|
+
publisher = self._publishers.get(address)
|
|
180
|
+
if publisher is None:
|
|
181
|
+
publisher = self._conn.publisher(address)
|
|
182
|
+
self._publishers[address] = publisher
|
|
183
|
+
proton_msg = ProtonMessage(body=encode_body(message.body))
|
|
184
|
+
proton_msg.inferred = True
|
|
185
|
+
if message.routing_key:
|
|
186
|
+
proton_msg.subject = message.routing_key
|
|
187
|
+
correlation_id = (message.attributes or {}).get("correlation_id")
|
|
188
|
+
if correlation_id is not None:
|
|
189
|
+
proton_msg.correlation_id = correlation_id
|
|
190
|
+
reply_to = (message.attributes or {}).get("reply_to")
|
|
191
|
+
if reply_to:
|
|
192
|
+
proton_msg.reply_to = reply_to
|
|
193
|
+
if message.attributes:
|
|
194
|
+
proton_msg.properties = {
|
|
195
|
+
key: value
|
|
196
|
+
for key, value in message.attributes.items()
|
|
197
|
+
if key not in {"correlation_id", "reply_to"}
|
|
198
|
+
and isinstance(value, (str, int, float, bool))
|
|
199
|
+
}
|
|
200
|
+
return publisher.publish(proton_msg)
|
|
201
|
+
|
|
202
|
+
def consume(
|
|
203
|
+
self,
|
|
204
|
+
queue_id: str,
|
|
205
|
+
handler: Callable[[Message], None],
|
|
206
|
+
*,
|
|
207
|
+
prefetch: Optional[int] = None,
|
|
208
|
+
) -> None:
|
|
185
209
|
destination = rabbitmq_queue_address(queue_id)
|
|
186
210
|
wrapped = _CallbackHandler(handler)
|
|
187
|
-
|
|
211
|
+
consumer_kwargs: dict[str, Any] = {"message_handler": wrapped}
|
|
212
|
+
if prefetch is not None:
|
|
213
|
+
consumer_kwargs["credit"] = prefetch
|
|
214
|
+
self._consumer = self._conn.consumer(destination, **consumer_kwargs)
|
|
188
215
|
self._consumer.run()
|
|
189
216
|
|
|
190
217
|
def stop(self) -> None:
|
|
@@ -193,6 +220,9 @@ class RabbitMqEngine(QueueEngine):
|
|
|
193
220
|
|
|
194
221
|
def close(self) -> None:
|
|
195
222
|
self.stop()
|
|
223
|
+
for publisher in self._publishers.values():
|
|
224
|
+
publisher.close()
|
|
225
|
+
self._publishers.clear()
|
|
196
226
|
self._conn.close()
|
|
197
227
|
|
|
198
228
|
def _publish_address(self, destination: str, routing_key: str) -> str:
|
|
@@ -24,6 +24,14 @@ from .constants import (
|
|
|
24
24
|
)
|
|
25
25
|
|
|
26
26
|
|
|
27
|
+
def __getattr__(name: str) -> Any:
|
|
28
|
+
if name == "Converter":
|
|
29
|
+
from rabbitmq_amqp_python_client.utils import Converter
|
|
30
|
+
|
|
31
|
+
return Converter
|
|
32
|
+
raise AttributeError(name)
|
|
33
|
+
|
|
34
|
+
|
|
27
35
|
def encode_body(body: Any) -> bytes:
|
|
28
36
|
if isinstance(body, bytes):
|
|
29
37
|
return body
|
|
File without changes
|
|
File without changes
|