rabbitmq-amqp-python-client 0.3.0__tar.gz → 0.4.1__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.
Potentially problematic release.
This version of rabbitmq-amqp-python-client might be problematic. Click here for more details.
- {rabbitmq_amqp_python_client-0.3.0 → rabbitmq_amqp_python_client-0.4.1}/PKG-INFO +2 -2
- {rabbitmq_amqp_python_client-0.3.0 → rabbitmq_amqp_python_client-0.4.1}/pyproject.toml +3 -3
- {rabbitmq_amqp_python_client-0.3.0 → rabbitmq_amqp_python_client-0.4.1}/rabbitmq_amqp_python_client/__init__.py +2 -0
- {rabbitmq_amqp_python_client-0.3.0 → rabbitmq_amqp_python_client-0.4.1}/rabbitmq_amqp_python_client/connection.py +6 -6
- {rabbitmq_amqp_python_client-0.3.0 → rabbitmq_amqp_python_client-0.4.1}/rabbitmq_amqp_python_client/consumer.py +59 -26
- {rabbitmq_amqp_python_client-0.3.0 → rabbitmq_amqp_python_client-0.4.1}/rabbitmq_amqp_python_client/entities.py +37 -4
- {rabbitmq_amqp_python_client-0.3.0 → rabbitmq_amqp_python_client-0.4.1}/rabbitmq_amqp_python_client/options.py +24 -3
- {rabbitmq_amqp_python_client-0.3.0 → rabbitmq_amqp_python_client-0.4.1}/rabbitmq_amqp_python_client/publisher.py +1 -1
- {rabbitmq_amqp_python_client-0.3.0 → rabbitmq_amqp_python_client-0.4.1}/rabbitmq_amqp_python_client/qpid/proton/_data.py +1 -0
- {rabbitmq_amqp_python_client-0.3.0 → rabbitmq_amqp_python_client-0.4.1}/rabbitmq_amqp_python_client/qpid/proton/_utils.py +14 -0
- {rabbitmq_amqp_python_client-0.3.0 → rabbitmq_amqp_python_client-0.4.1}/LICENSE +0 -0
- {rabbitmq_amqp_python_client-0.3.0 → rabbitmq_amqp_python_client-0.4.1}/README.md +0 -0
- {rabbitmq_amqp_python_client-0.3.0 → rabbitmq_amqp_python_client-0.4.1}/rabbitmq_amqp_python_client/address_helper.py +0 -0
- {rabbitmq_amqp_python_client-0.3.0 → rabbitmq_amqp_python_client-0.4.1}/rabbitmq_amqp_python_client/amqp_consumer_handler.py +0 -0
- {rabbitmq_amqp_python_client-0.3.0 → rabbitmq_amqp_python_client-0.4.1}/rabbitmq_amqp_python_client/asyncio/__init__.py +0 -0
- {rabbitmq_amqp_python_client-0.3.0 → rabbitmq_amqp_python_client-0.4.1}/rabbitmq_amqp_python_client/asyncio/connection.py +0 -0
- {rabbitmq_amqp_python_client-0.3.0 → rabbitmq_amqp_python_client-0.4.1}/rabbitmq_amqp_python_client/asyncio/consumer.py +0 -0
- {rabbitmq_amqp_python_client-0.3.0 → rabbitmq_amqp_python_client-0.4.1}/rabbitmq_amqp_python_client/asyncio/environment.py +0 -0
- {rabbitmq_amqp_python_client-0.3.0 → rabbitmq_amqp_python_client-0.4.1}/rabbitmq_amqp_python_client/asyncio/management.py +0 -0
- {rabbitmq_amqp_python_client-0.3.0 → rabbitmq_amqp_python_client-0.4.1}/rabbitmq_amqp_python_client/asyncio/publisher.py +0 -0
- {rabbitmq_amqp_python_client-0.3.0 → rabbitmq_amqp_python_client-0.4.1}/rabbitmq_amqp_python_client/common.py +0 -0
- {rabbitmq_amqp_python_client-0.3.0 → rabbitmq_amqp_python_client-0.4.1}/rabbitmq_amqp_python_client/delivery_context.py +0 -0
- {rabbitmq_amqp_python_client-0.3.0 → rabbitmq_amqp_python_client-0.4.1}/rabbitmq_amqp_python_client/environment.py +0 -0
- {rabbitmq_amqp_python_client-0.3.0 → rabbitmq_amqp_python_client-0.4.1}/rabbitmq_amqp_python_client/exceptions.py +0 -0
- {rabbitmq_amqp_python_client-0.3.0 → rabbitmq_amqp_python_client-0.4.1}/rabbitmq_amqp_python_client/management.py +0 -0
- {rabbitmq_amqp_python_client-0.3.0 → rabbitmq_amqp_python_client-0.4.1}/rabbitmq_amqp_python_client/py.typed +0 -0
- {rabbitmq_amqp_python_client-0.3.0 → rabbitmq_amqp_python_client-0.4.1}/rabbitmq_amqp_python_client/qpid/proton/__init__.py +0 -0
- {rabbitmq_amqp_python_client-0.3.0 → rabbitmq_amqp_python_client-0.4.1}/rabbitmq_amqp_python_client/qpid/proton/_common.py +0 -0
- {rabbitmq_amqp_python_client-0.3.0 → rabbitmq_amqp_python_client-0.4.1}/rabbitmq_amqp_python_client/qpid/proton/_condition.py +0 -0
- {rabbitmq_amqp_python_client-0.3.0 → rabbitmq_amqp_python_client-0.4.1}/rabbitmq_amqp_python_client/qpid/proton/_delivery.py +0 -0
- {rabbitmq_amqp_python_client-0.3.0 → rabbitmq_amqp_python_client-0.4.1}/rabbitmq_amqp_python_client/qpid/proton/_endpoints.py +0 -0
- {rabbitmq_amqp_python_client-0.3.0 → rabbitmq_amqp_python_client-0.4.1}/rabbitmq_amqp_python_client/qpid/proton/_events.py +0 -0
- {rabbitmq_amqp_python_client-0.3.0 → rabbitmq_amqp_python_client-0.4.1}/rabbitmq_amqp_python_client/qpid/proton/_exceptions.py +0 -0
- {rabbitmq_amqp_python_client-0.3.0 → rabbitmq_amqp_python_client-0.4.1}/rabbitmq_amqp_python_client/qpid/proton/_handler.py +0 -0
- {rabbitmq_amqp_python_client-0.3.0 → rabbitmq_amqp_python_client-0.4.1}/rabbitmq_amqp_python_client/qpid/proton/_handlers.py +0 -0
- {rabbitmq_amqp_python_client-0.3.0 → rabbitmq_amqp_python_client-0.4.1}/rabbitmq_amqp_python_client/qpid/proton/_io.py +0 -0
- {rabbitmq_amqp_python_client-0.3.0 → rabbitmq_amqp_python_client-0.4.1}/rabbitmq_amqp_python_client/qpid/proton/_message.py +0 -0
- {rabbitmq_amqp_python_client-0.3.0 → rabbitmq_amqp_python_client-0.4.1}/rabbitmq_amqp_python_client/qpid/proton/_reactor.py +0 -0
- {rabbitmq_amqp_python_client-0.3.0 → rabbitmq_amqp_python_client-0.4.1}/rabbitmq_amqp_python_client/qpid/proton/_selectable.py +0 -0
- {rabbitmq_amqp_python_client-0.3.0 → rabbitmq_amqp_python_client-0.4.1}/rabbitmq_amqp_python_client/qpid/proton/_tracing.py +0 -0
- {rabbitmq_amqp_python_client-0.3.0 → rabbitmq_amqp_python_client-0.4.1}/rabbitmq_amqp_python_client/qpid/proton/_transport.py +0 -0
- {rabbitmq_amqp_python_client-0.3.0 → rabbitmq_amqp_python_client-0.4.1}/rabbitmq_amqp_python_client/qpid/proton/_url.py +0 -0
- {rabbitmq_amqp_python_client-0.3.0 → rabbitmq_amqp_python_client-0.4.1}/rabbitmq_amqp_python_client/qpid/proton/_wrapper.py +0 -0
- {rabbitmq_amqp_python_client-0.3.0 → rabbitmq_amqp_python_client-0.4.1}/rabbitmq_amqp_python_client/qpid/proton/handlers.py +0 -0
- {rabbitmq_amqp_python_client-0.3.0 → rabbitmq_amqp_python_client-0.4.1}/rabbitmq_amqp_python_client/qpid/proton/py.typed +0 -0
- {rabbitmq_amqp_python_client-0.3.0 → rabbitmq_amqp_python_client-0.4.1}/rabbitmq_amqp_python_client/qpid/proton/reactor.py +0 -0
- {rabbitmq_amqp_python_client-0.3.0 → rabbitmq_amqp_python_client-0.4.1}/rabbitmq_amqp_python_client/qpid/proton/tracing.py +0 -0
- {rabbitmq_amqp_python_client-0.3.0 → rabbitmq_amqp_python_client-0.4.1}/rabbitmq_amqp_python_client/qpid/proton/utils.py +0 -0
- {rabbitmq_amqp_python_client-0.3.0 → rabbitmq_amqp_python_client-0.4.1}/rabbitmq_amqp_python_client/queues.py +0 -0
- {rabbitmq_amqp_python_client-0.3.0 → rabbitmq_amqp_python_client-0.4.1}/rabbitmq_amqp_python_client/ssl_configuration.py +0 -0
- {rabbitmq_amqp_python_client-0.3.0 → rabbitmq_amqp_python_client-0.4.1}/rabbitmq_amqp_python_client/utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: rabbitmq-amqp-python-client
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.1
|
|
4
4
|
Summary: Python RabbitMQ client for AMQP 1.0 protocol
|
|
5
5
|
License: Apache-2.0 license
|
|
6
6
|
License-File: LICENSE
|
|
@@ -15,7 +15,7 @@ Classifier: Programming Language :: Python :: 3.12
|
|
|
15
15
|
Classifier: Programming Language :: Python :: 3.13
|
|
16
16
|
Classifier: Programming Language :: Python :: 3.14
|
|
17
17
|
Requires-Dist: packaging (>=23.0,<24.0)
|
|
18
|
-
Requires-Dist: python-qpid-proton (>=0.
|
|
18
|
+
Requires-Dist: python-qpid-proton (>=0.40.0,<0.41.0)
|
|
19
19
|
Requires-Dist: typing-extensions (>=4.13.0,<5.0.0)
|
|
20
20
|
Description-Content-Type: text/markdown
|
|
21
21
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "rabbitmq-amqp-python-client"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.4.1"
|
|
4
4
|
description = "Python RabbitMQ client for AMQP 1.0 protocol"
|
|
5
5
|
authors = ["RabbitMQ team"]
|
|
6
6
|
license = "Apache-2.0 license"
|
|
@@ -8,7 +8,7 @@ readme = "README.md"
|
|
|
8
8
|
|
|
9
9
|
[tool.poetry.dependencies]
|
|
10
10
|
python = "^3.9"
|
|
11
|
-
python-qpid-proton = "^0.
|
|
11
|
+
python-qpid-proton = "^0.40.0"
|
|
12
12
|
typing-extensions = "^4.13.0"
|
|
13
13
|
packaging = "^23.0"
|
|
14
14
|
|
|
@@ -21,7 +21,7 @@ isort = "^5.9.3"
|
|
|
21
21
|
mypy = "^0.910"
|
|
22
22
|
pytest = "^8.3.4"
|
|
23
23
|
black = "^24.3.0"
|
|
24
|
-
python-qpid-proton = "^0.
|
|
24
|
+
python-qpid-proton = "^0.40.0"
|
|
25
25
|
requests = "^2.31.0"
|
|
26
26
|
pytest-asyncio = "^1.2.0"
|
|
27
27
|
|
|
@@ -14,6 +14,7 @@ from .connection import Connection
|
|
|
14
14
|
from .consumer import Consumer
|
|
15
15
|
from .entities import (
|
|
16
16
|
ConsumerOptions,
|
|
17
|
+
DirectReplyToConsumerOptions,
|
|
17
18
|
ExchangeCustomSpecification,
|
|
18
19
|
ExchangeSpecification,
|
|
19
20
|
ExchangeToExchangeBindingSpecification,
|
|
@@ -72,6 +73,7 @@ __all__ = [
|
|
|
72
73
|
"QuorumQueueSpecification",
|
|
73
74
|
"ClassicQueueSpecification",
|
|
74
75
|
"StreamSpecification",
|
|
76
|
+
"DirectReplyToConsumerOptions",
|
|
75
77
|
"ExchangeToQueueBindingSpecification",
|
|
76
78
|
"ExchangeToExchangeBindingSpecification",
|
|
77
79
|
"QueueType",
|
|
@@ -379,7 +379,7 @@ class Connection:
|
|
|
379
379
|
|
|
380
380
|
def consumer(
|
|
381
381
|
self,
|
|
382
|
-
destination: str,
|
|
382
|
+
destination: Optional[str] = None,
|
|
383
383
|
message_handler: Optional[MessagingHandler] = None,
|
|
384
384
|
consumer_options: Optional[ConsumerOptions] = None,
|
|
385
385
|
credit: Optional[int] = None,
|
|
@@ -388,7 +388,7 @@ class Connection:
|
|
|
388
388
|
Create a new consumer instance.
|
|
389
389
|
|
|
390
390
|
Args:
|
|
391
|
-
destination: The address to consume from
|
|
391
|
+
destination: Optional The address to consume from
|
|
392
392
|
message_handler: Optional handler for processing messages
|
|
393
393
|
consumer_options: Optional configuration for queue consumption. Each queue has its own consumer options.
|
|
394
394
|
credit: Optional credit value for flow control
|
|
@@ -397,9 +397,11 @@ class Connection:
|
|
|
397
397
|
Consumer: A new consumer instance
|
|
398
398
|
|
|
399
399
|
Raises:
|
|
400
|
-
ArgumentOutOfRangeException: If destination address format is invalid
|
|
400
|
+
ArgumentOutOfRangeException: If destination address format is invalid.
|
|
401
|
+
Only applies if not using Direct Reply-to.
|
|
402
|
+
The server will provide the queue name in that case.
|
|
401
403
|
"""
|
|
402
|
-
if not validate_address(destination):
|
|
404
|
+
if destination is not None and not validate_address(destination):
|
|
403
405
|
raise ArgumentOutOfRangeException(
|
|
404
406
|
"destination address must start with /queues or /exchanges"
|
|
405
407
|
)
|
|
@@ -438,9 +440,7 @@ class Connection:
|
|
|
438
440
|
time.sleep(delay.total_seconds())
|
|
439
441
|
|
|
440
442
|
try:
|
|
441
|
-
|
|
442
443
|
self._open_connections(reconnect_handlers=True)
|
|
443
|
-
|
|
444
444
|
self._connections.append(self)
|
|
445
445
|
|
|
446
446
|
except ConnectionException as e:
|
|
@@ -2,7 +2,11 @@ import logging
|
|
|
2
2
|
from typing import Literal, Optional, Union, cast
|
|
3
3
|
|
|
4
4
|
from .amqp_consumer_handler import AMQPMessagingHandler
|
|
5
|
-
from .entities import
|
|
5
|
+
from .entities import (
|
|
6
|
+
ConsumerOptions,
|
|
7
|
+
DirectReplyToConsumerOptions,
|
|
8
|
+
StreamConsumerOptions,
|
|
9
|
+
)
|
|
6
10
|
from .options import (
|
|
7
11
|
ReceiverOptionUnsettled,
|
|
8
12
|
ReceiverOptionUnsettledWithFilters,
|
|
@@ -29,16 +33,16 @@ class Consumer:
|
|
|
29
33
|
_conn (BlockingConnection): The underlying connection to RabbitMQ
|
|
30
34
|
_addr (str): The address to consume from
|
|
31
35
|
_handler (Optional[MessagingHandler]): Optional message handling callback
|
|
32
|
-
|
|
36
|
+
_consumer_options (Optional[StreamConsumerOptions]): Configuration for stream consumption
|
|
33
37
|
_credit (Optional[int]): Flow control credit value
|
|
34
38
|
"""
|
|
35
39
|
|
|
36
40
|
def __init__(
|
|
37
41
|
self,
|
|
38
42
|
conn: BlockingConnection,
|
|
39
|
-
addr: str,
|
|
43
|
+
addr: Optional[str] = None,
|
|
40
44
|
handler: Optional[AMQPMessagingHandler] = None,
|
|
41
|
-
|
|
45
|
+
consumer_options: Optional[ConsumerOptions] = None,
|
|
42
46
|
credit: Optional[int] = None,
|
|
43
47
|
):
|
|
44
48
|
"""
|
|
@@ -48,14 +52,14 @@ class Consumer:
|
|
|
48
52
|
conn: The blocking connection to use for consuming
|
|
49
53
|
addr: The address to consume from
|
|
50
54
|
handler: Optional message handler for processing received messages
|
|
51
|
-
|
|
55
|
+
consumer_options: Optional configuration for stream-based consumption
|
|
52
56
|
credit: Optional credit value for flow control
|
|
53
57
|
"""
|
|
54
58
|
self._receiver: Optional[BlockingReceiver] = None
|
|
55
59
|
self._conn = conn
|
|
56
60
|
self._addr = addr
|
|
57
61
|
self._handler = handler
|
|
58
|
-
self.
|
|
62
|
+
self._consumer_options = consumer_options
|
|
59
63
|
self._credit = credit
|
|
60
64
|
self._consumers: list[Consumer] = []
|
|
61
65
|
self._open()
|
|
@@ -66,21 +70,25 @@ class Consumer:
|
|
|
66
70
|
self._receiver = self._create_receiver(self._addr)
|
|
67
71
|
|
|
68
72
|
def _update_connection(self, conn: BlockingConnection) -> None:
|
|
73
|
+
addr = ""
|
|
74
|
+
if self._addr is not None:
|
|
75
|
+
addr = self._addr
|
|
76
|
+
|
|
69
77
|
self._conn = conn
|
|
70
|
-
if self.
|
|
78
|
+
if self._consumer_options is None:
|
|
71
79
|
logger.debug("creating new receiver without stream")
|
|
72
80
|
self._receiver = self._conn.create_receiver(
|
|
73
|
-
|
|
74
|
-
options=ReceiverOptionUnsettled(
|
|
81
|
+
addr,
|
|
82
|
+
options=ReceiverOptionUnsettled(addr),
|
|
75
83
|
handler=self._handler,
|
|
76
84
|
)
|
|
77
85
|
else:
|
|
78
86
|
logger.debug("creating new stream receiver")
|
|
79
|
-
self.
|
|
87
|
+
self._consumer_options.offset(self._handler.offset - 1) # type: ignore
|
|
80
88
|
self._receiver = self._conn.create_receiver(
|
|
81
|
-
|
|
89
|
+
addr,
|
|
82
90
|
options=ReceiverOptionUnsettledWithFilters(
|
|
83
|
-
|
|
91
|
+
addr, self._consumer_options
|
|
84
92
|
),
|
|
85
93
|
handler=self._handler,
|
|
86
94
|
)
|
|
@@ -142,29 +150,54 @@ class Consumer:
|
|
|
142
150
|
self._receiver.container.stop_events()
|
|
143
151
|
self._receiver.container.stop()
|
|
144
152
|
|
|
145
|
-
def _create_receiver(self, addr: str) -> BlockingReceiver:
|
|
146
|
-
|
|
147
|
-
if self.
|
|
148
|
-
|
|
149
|
-
addr, options=ReceiverOptionUnsettled(addr), handler=self._handler
|
|
150
|
-
)
|
|
153
|
+
def _create_receiver(self, addr: Optional[str] = None) -> BlockingReceiver:
|
|
154
|
+
credit = 10
|
|
155
|
+
if self._credit is not None:
|
|
156
|
+
credit = self._credit
|
|
151
157
|
|
|
158
|
+
if self._consumer_options is not None:
|
|
159
|
+
logger.debug(
|
|
160
|
+
"Creating the receiver, with options: %s",
|
|
161
|
+
type(self._consumer_options).__name__,
|
|
162
|
+
)
|
|
152
163
|
else:
|
|
153
|
-
receiver
|
|
164
|
+
logger.debug("Creating the receiver, without options")
|
|
165
|
+
|
|
166
|
+
if self._consumer_options is None:
|
|
167
|
+
return self._conn.create_receiver(
|
|
154
168
|
addr,
|
|
155
|
-
options=
|
|
169
|
+
options=ReceiverOptionUnsettled(addr),
|
|
156
170
|
handler=self._handler,
|
|
171
|
+
credit=credit,
|
|
157
172
|
)
|
|
158
173
|
|
|
159
|
-
if self.
|
|
160
|
-
|
|
174
|
+
if isinstance(self._consumer_options, DirectReplyToConsumerOptions):
|
|
175
|
+
logger.debug("Creating dynamic receiver for direct reply-to")
|
|
176
|
+
dynamic_receiver = self._conn.create_dynamic_receiver(
|
|
177
|
+
credit, handler=self._handler
|
|
178
|
+
)
|
|
179
|
+
dynamic_receiver.credit = credit
|
|
180
|
+
return dynamic_receiver
|
|
161
181
|
|
|
162
|
-
|
|
182
|
+
if isinstance(self._consumer_options, StreamConsumerOptions):
|
|
183
|
+
return self._conn.create_receiver(
|
|
184
|
+
addr,
|
|
185
|
+
options=ReceiverOptionUnsettledWithFilters(
|
|
186
|
+
addr, self._consumer_options
|
|
187
|
+
),
|
|
188
|
+
handler=self._handler,
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
raise Exception(
|
|
192
|
+
"Receiver is not initialized. No valid consumer options provided."
|
|
193
|
+
)
|
|
163
194
|
|
|
164
195
|
@property
|
|
165
|
-
def address(self) -> str:
|
|
166
|
-
|
|
167
|
-
|
|
196
|
+
def address(self) -> Optional[str]:
|
|
197
|
+
if self._receiver is not None:
|
|
198
|
+
return cast(Optional[str], self._receiver.link.remote_source.address)
|
|
199
|
+
else:
|
|
200
|
+
raise Exception("Receiver is not initialized")
|
|
168
201
|
|
|
169
202
|
@property
|
|
170
203
|
def handler(self) -> Optional[AMQPMessagingHandler]:
|
|
@@ -5,7 +5,7 @@ from typing import Any, Dict, Optional, Union
|
|
|
5
5
|
|
|
6
6
|
from .common import ExchangeType, QueueType
|
|
7
7
|
from .exceptions import ValidationCodeException
|
|
8
|
-
from .qpid.proton._data import Described, symbol
|
|
8
|
+
from .qpid.proton._data import Described, symbol, timestamp
|
|
9
9
|
|
|
10
10
|
SQL_FILTER = "sql-filter"
|
|
11
11
|
AMQP_SQL_FILTER = "amqp:sql-filter"
|
|
@@ -109,11 +109,13 @@ class OffsetSpecification(Enum):
|
|
|
109
109
|
first: Start from the first message in the stream
|
|
110
110
|
next: Start from the next message to arrive
|
|
111
111
|
last: Start from the last message in the stream
|
|
112
|
+
timestamp: Start from a specific timestamp
|
|
112
113
|
"""
|
|
113
114
|
|
|
114
115
|
first = ("first",)
|
|
115
116
|
next = ("next",)
|
|
116
117
|
last = ("last",)
|
|
118
|
+
timestamp = ("timestamp",)
|
|
117
119
|
|
|
118
120
|
|
|
119
121
|
@dataclass
|
|
@@ -160,6 +162,9 @@ class ConsumerOptions:
|
|
|
160
162
|
def filter_set(self) -> Dict[symbol, Described]:
|
|
161
163
|
raise NotImplementedError("Subclasses should implement this method")
|
|
162
164
|
|
|
165
|
+
def direct_reply_to(self) -> bool:
|
|
166
|
+
return False
|
|
167
|
+
|
|
163
168
|
|
|
164
169
|
@dataclass
|
|
165
170
|
class MessageProperties:
|
|
@@ -240,7 +245,9 @@ class StreamConsumerOptions(ConsumerOptions):
|
|
|
240
245
|
|
|
241
246
|
def __init__(
|
|
242
247
|
self,
|
|
243
|
-
offset_specification: Optional[
|
|
248
|
+
offset_specification: Optional[
|
|
249
|
+
Union[OffsetSpecification, int, datetime]
|
|
250
|
+
] = None,
|
|
244
251
|
filter_options: Optional[StreamFilterOptions] = None,
|
|
245
252
|
):
|
|
246
253
|
|
|
@@ -272,7 +279,9 @@ class StreamConsumerOptions(ConsumerOptions):
|
|
|
272
279
|
if filter_options.sql is not None and filter_options.sql != "":
|
|
273
280
|
self._filter_sql(filter_options.sql)
|
|
274
281
|
|
|
275
|
-
def _offset(
|
|
282
|
+
def _offset(
|
|
283
|
+
self, offset_specification: Union[OffsetSpecification, int, datetime]
|
|
284
|
+
) -> None:
|
|
276
285
|
"""
|
|
277
286
|
Set the offset specification for the stream.
|
|
278
287
|
|
|
@@ -284,11 +293,20 @@ class StreamConsumerOptions(ConsumerOptions):
|
|
|
284
293
|
self._filter_set[symbol(STREAM_OFFSET_SPEC)] = Described(
|
|
285
294
|
symbol(STREAM_OFFSET_SPEC), offset_specification
|
|
286
295
|
)
|
|
287
|
-
|
|
296
|
+
if isinstance(offset_specification, OffsetSpecification):
|
|
288
297
|
self._filter_set[symbol(STREAM_OFFSET_SPEC)] = Described(
|
|
289
298
|
symbol(STREAM_OFFSET_SPEC), offset_specification.name
|
|
290
299
|
)
|
|
291
300
|
|
|
301
|
+
if isinstance(offset_specification, datetime):
|
|
302
|
+
# convert datetime to milliseconds since epoch
|
|
303
|
+
milliseconds_since_epoch = int(offset_specification.timestamp() * 1000)
|
|
304
|
+
# Create a timestamp instance
|
|
305
|
+
ts = timestamp(milliseconds_since_epoch)
|
|
306
|
+
self._filter_set[symbol(STREAM_OFFSET_SPEC)] = Described(
|
|
307
|
+
symbol(STREAM_OFFSET_SPEC), ts
|
|
308
|
+
)
|
|
309
|
+
|
|
292
310
|
def _filter_values(self, filters: list[str]) -> None:
|
|
293
311
|
"""
|
|
294
312
|
Set the filter values for the stream.
|
|
@@ -400,6 +418,21 @@ class StreamConsumerOptions(ConsumerOptions):
|
|
|
400
418
|
)
|
|
401
419
|
|
|
402
420
|
|
|
421
|
+
class DirectReplyToConsumerOptions(ConsumerOptions):
|
|
422
|
+
|
|
423
|
+
def validate(self, versions: Dict[str, bool]) -> None:
|
|
424
|
+
if not versions.get("4.2.0", False):
|
|
425
|
+
raise ValidationCodeException(
|
|
426
|
+
"Direct Reply-To requires RabbitMQ 4.2.0 or higher"
|
|
427
|
+
)
|
|
428
|
+
|
|
429
|
+
def filter_set(self) -> Dict[symbol, Described]:
|
|
430
|
+
return {}
|
|
431
|
+
|
|
432
|
+
def direct_reply_to(self) -> bool:
|
|
433
|
+
return True
|
|
434
|
+
|
|
435
|
+
|
|
403
436
|
@dataclass
|
|
404
437
|
class RecoveryConfiguration:
|
|
405
438
|
"""
|
|
@@ -1,9 +1,15 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
|
|
1
3
|
from .entities import ConsumerOptions
|
|
2
4
|
from .qpid.proton._data import ( # noqa: E402
|
|
5
|
+
Data,
|
|
3
6
|
PropertyDict,
|
|
4
7
|
symbol,
|
|
5
8
|
)
|
|
6
|
-
from .qpid.proton._endpoints import
|
|
9
|
+
from .qpid.proton._endpoints import ( # noqa: E402
|
|
10
|
+
Link,
|
|
11
|
+
Terminus,
|
|
12
|
+
)
|
|
7
13
|
from .qpid.proton.reactor import ( # noqa: E402
|
|
8
14
|
Filter,
|
|
9
15
|
LinkOption,
|
|
@@ -52,8 +58,23 @@ class ReceiverOption(LinkOption): # type: ignore
|
|
|
52
58
|
link.source.dynamic = False
|
|
53
59
|
|
|
54
60
|
|
|
61
|
+
class DynamicReceiverOption(LinkOption): # type: ignore
|
|
62
|
+
|
|
63
|
+
def apply(self, link: Link) -> None:
|
|
64
|
+
link.snd_settle_mode = Link.SND_SETTLED
|
|
65
|
+
# link.rcv_settle_mode = Link.RCV_FIRST
|
|
66
|
+
link.source.expiry_policy = Terminus.EXPIRE_WITH_LINK
|
|
67
|
+
link.properties = PropertyDict({symbol("paired"): True})
|
|
68
|
+
link.source.dynamic = True
|
|
69
|
+
data = link.source.capabilities
|
|
70
|
+
data.put_array(False, Data.SYMBOL)
|
|
71
|
+
data.enter()
|
|
72
|
+
data.put_string("rabbitmq:volatile-queue")
|
|
73
|
+
data.exit()
|
|
74
|
+
|
|
75
|
+
|
|
55
76
|
class ReceiverOptionUnsettled(LinkOption): # type: ignore
|
|
56
|
-
def __init__(self, addr: str):
|
|
77
|
+
def __init__(self, addr: Optional[str]):
|
|
57
78
|
self._addr = addr
|
|
58
79
|
|
|
59
80
|
def apply(self, link: Link) -> None:
|
|
@@ -68,7 +89,7 @@ class ReceiverOptionUnsettled(LinkOption): # type: ignore
|
|
|
68
89
|
|
|
69
90
|
|
|
70
91
|
class ReceiverOptionUnsettledWithFilters(Filter): # type: ignore
|
|
71
|
-
def __init__(self, addr: str, consumer_options: ConsumerOptions):
|
|
92
|
+
def __init__(self, addr: Optional[str], consumer_options: ConsumerOptions):
|
|
72
93
|
super().__init__(consumer_options.filter_set())
|
|
73
94
|
self._addr = addr
|
|
74
95
|
|
|
@@ -94,7 +94,7 @@ class Publisher:
|
|
|
94
94
|
return self._sender.send(message)
|
|
95
95
|
else:
|
|
96
96
|
if message.address != "":
|
|
97
|
-
if validate_address(message.address)
|
|
97
|
+
if not validate_address(message.address):
|
|
98
98
|
raise ArgumentOutOfRangeException(
|
|
99
99
|
"destination address must start with /queues or /exchanges"
|
|
100
100
|
)
|
|
@@ -44,6 +44,7 @@ from ._handlers import (
|
|
|
44
44
|
)
|
|
45
45
|
from ._reactor import Container
|
|
46
46
|
from ._url import Url
|
|
47
|
+
from ...options import DynamicReceiverOption
|
|
47
48
|
|
|
48
49
|
try:
|
|
49
50
|
from typing import Literal
|
|
@@ -505,6 +506,17 @@ class BlockingConnection(Handler):
|
|
|
505
506
|
),
|
|
506
507
|
)
|
|
507
508
|
|
|
509
|
+
def create_dynamic_receiver(
|
|
510
|
+
self, credit: Optional[int] = None, handler: Optional[Handler] = None
|
|
511
|
+
):
|
|
512
|
+
return self.create_receiver(
|
|
513
|
+
credit=credit,
|
|
514
|
+
dynamic=True,
|
|
515
|
+
options=DynamicReceiverOption(),
|
|
516
|
+
handler=handler,
|
|
517
|
+
name="dynamic-receiver_" + str(id(self)),
|
|
518
|
+
)
|
|
519
|
+
|
|
508
520
|
def create_receiver(
|
|
509
521
|
self,
|
|
510
522
|
address: Optional[str] = None,
|
|
@@ -514,6 +526,8 @@ class BlockingConnection(Handler):
|
|
|
514
526
|
name: Optional[str] = None,
|
|
515
527
|
options: Optional[
|
|
516
528
|
Union[
|
|
529
|
+
"DynamicReceiverOption",
|
|
530
|
+
List["DynamicReceiverOption"],
|
|
517
531
|
"ReceiverOption",
|
|
518
532
|
List["ReceiverOption"],
|
|
519
533
|
"LinkOption",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|