acemq_amqp.transport
What a broker has to provide, described without naming one.
Everything above this module — the envelope, the codecs, the retry engine, the topology — is written against these types rather than against a client library. That is what makes a fake transport in a test exercise the same code path as RabbitMQ does in production, rather than a second implementation of it that can drift.
The types are deliberately thin. A transport moves bytes and settles deliveries;
it does not know what an envelope is, which is why Outbound carries a
plain header map and Delivery hands one back.
1# Copyright 2026 AceMQ. 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# https://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14 15"""What a broker has to provide, described without naming one. 16 17Everything above this module — the envelope, the codecs, the retry engine, the 18topology — is written against these types rather than against a client library. 19That is what makes a fake transport in a test exercise the same code path as 20RabbitMQ does in production, rather than a second implementation of it that can 21drift. 22 23The types are deliberately thin. A transport moves bytes and settles deliveries; 24it does not know what an envelope is, which is why :class:`Outbound` carries a 25plain header map and :class:`Delivery` hands one back. 26""" 27 28from __future__ import annotations 29 30from collections.abc import Awaitable, Callable, Mapping 31from dataclasses import dataclass, field 32from typing import Any, Protocol, runtime_checkable 33 34 35@dataclass(frozen=True, slots=True) 36class QueueSpec: 37 """How a queue should be declared. 38 39 :param durable: survives a broker restart 40 :param auto_delete: goes away when its last consumer does 41 :param exclusive: usable only by the connection that declared it 42 :param args: broker-specific arguments, such as ``x-dead-letter-exchange`` 43 """ 44 45 durable: bool = True 46 auto_delete: bool = False 47 exclusive: bool = False 48 args: Mapping[str, Any] = field(default_factory=dict) 49 50 51@dataclass(frozen=True, slots=True) 52class ExchangeSpec: 53 """How an exchange should be declared. 54 55 :param kind: ``direct``, ``topic``, ``fanout`` or ``headers`` 56 :param durable: survives a broker restart 57 :param auto_delete: goes away when its last binding does 58 :param args: broker-specific arguments 59 """ 60 61 kind: str = "topic" 62 durable: bool = True 63 auto_delete: bool = False 64 args: Mapping[str, Any] = field(default_factory=dict) 65 66 67@dataclass(frozen=True, slots=True) 68class ConsumeSpec: 69 """How a consumer should be set up. 70 71 :param prefetch: how many unacknowledged messages the broker will send at 72 once, or zero for the transport's default 73 :param tag: what to call this consumer to the broker, which is what an 74 operator sees when working out who is holding a message 75 :param args: broker-specific consumer arguments, such as the 76 ``x-stream-offset`` a stream consumer needs to say where it starts 77 """ 78 79 prefetch: int = 0 80 tag: str = "" 81 args: Mapping[str, Any] = field(default_factory=dict) 82 83 84@dataclass(frozen=True, slots=True) 85class Outbound: 86 """A message on its way to the broker, already encoded. 87 88 :param body: the encoded payload 89 :param content_type: what the body is, for the consumer's codec 90 :param message_id: the AMQP message id, conventionally the envelope's 91 :param reply_to: AMQP's own ``reply-to`` property, naming where an answer 92 should be sent. Empty for the overwhelming majority of messages, which 93 are not questions. It is here as well as in the headers because the four 94 other libraries read the native property, and a Python responder that 95 only ever wrote a header could not be answered by a Java or .NET caller 96 :param headers: the envelope's headers plus the application's 97 :param persistent: asks the broker to write the message to disk, which is 98 not a guarantee on its own — a persistent message on a queue that is not 99 durable still dies with the broker 100 :param mandatory: asks the broker to return the message rather than drop it 101 when no queue is bound to receive it 102 """ 103 104 body: bytes 105 content_type: str = "" 106 message_id: str = "" 107 reply_to: str = "" 108 headers: Mapping[str, Any] = field(default_factory=dict) 109 persistent: bool = True 110 mandatory: bool = False 111 112 113@dataclass(frozen=True, slots=True) 114class PublishResult: 115 """What the broker said about a published message. 116 117 :param message_id: the identifier it went out with 118 :param confirmed: the broker taking responsibility for the message. Without 119 publisher confirms this is false and nothing has been promised: the 120 message reached a socket, which is not the same thing 121 :param routed: false when a mandatory message reached no queue at all. A 122 message nothing is bound to receive is not an error to the broker; it is 123 dropped, silently, which is exactly the failure worth hearing about 124 :param return_reason: the broker's explanation when ``routed`` is false 125 """ 126 127 message_id: str = "" 128 confirmed: bool = False 129 routed: bool = True 130 return_reason: str = "" 131 132 133@dataclass(frozen=True, slots=True) 134class Delivery: 135 """A message as it arrived, before any codec has looked at it. 136 137 :param body: the bytes, undecoded 138 :param content_type: what the sender said the body is, or ``None`` when the 139 sender said nothing — which is a different thing from an empty string 140 and is why the codecs distinguish them 141 :param routing_key: the key it arrived under 142 :param message_id: the AMQP message id 143 :param headers: everything the sender wrote, reserved names included 144 :param redelivered: the broker saying it has handed this message over 145 before. It is the broker's own view and says nothing about which attempt 146 this is: a requeue returns the bytes the broker was given, so the 147 attempt header still reads whatever the publisher wrote 148 :param ack: confirms the message and removes it from the queue 149 :param nack: returns the message, requeued or not 150 :param reply_to: AMQP's own ``reply-to`` property, empty when the sender did 151 not set one. A transport that cannot report it leaves it empty and the 152 responder falls back to the header, which is the only reason this has a 153 default at all 154 """ 155 156 body: bytes 157 content_type: str | None 158 routing_key: str 159 message_id: str 160 headers: Mapping[str, Any] 161 redelivered: bool 162 ack: Callable[[], Awaitable[None]] 163 nack: Callable[[bool], Awaitable[None]] 164 reply_to: str = "" 165 166 167class Subscription(Protocol): 168 """A running consumer on a transport. 169 170 Stopping and releasing are two steps rather than one because a message that 171 has been delivered still has to be settled, and on AMQP a settlement travels 172 on the channel the delivery came in on. Closing that channel while a handler 173 is still working means acknowledging on a channel that has gone — which 174 RabbitMQ answers by closing the whole connection, taking down every other 175 consumer and publisher on it. 176 """ 177 178 async def stop(self) -> None: 179 """Stops delivery, leaving what has already arrived settleable. 180 181 It must not return until the transport has promised no further 182 deliveries, because that promise is what makes it safe for the consumer 183 above to shut its workers down. 184 """ 185 186 async def close(self) -> None: 187 """Releases what the subscription was holding. 188 189 Called once nothing is left unsettled. 190 """ 191 192 193@runtime_checkable 194class Transport(Protocol): 195 """A connection to a broker. 196 197 Async because that is the shape a broker client has: every one of these is a 198 round trip, and a library that hid them behind blocking calls would make one 199 slow broker stall a whole process. The blocking API in 200 :mod:`acemq_amqp.sync` is built on top of this rather than beside it. 201 """ 202 203 async def declare_queue(self, name: str, spec: QueueSpec) -> None: 204 """Creates a queue if it is not already there.""" 205 206 async def declare_exchange(self, name: str, spec: ExchangeSpec) -> None: 207 """Creates an exchange if it is not already there.""" 208 209 async def bind(self, queue: str, exchange: str, routing_key: str) -> None: 210 """Routes messages matching a key from an exchange to a queue.""" 211 212 async def publish( 213 self, exchange: str, routing_key: str, message: Outbound 214 ) -> PublishResult: 215 """Sends one message and reports what the broker said about it.""" 216 217 async def consume( 218 self, 219 queue: str, 220 spec: ConsumeSpec, 221 deliver: Callable[[Delivery], Awaitable[None]], 222 ) -> Subscription: 223 """Delivers messages to ``deliver`` until the subscription is closed.""" 224 225 async def close(self) -> None: 226 """Releases the connection.""" 227 228 229@runtime_checkable 230class BlockedState(Protocol): 231 """A transport that can say whether the broker has blocked its connection. 232 233 RabbitMQ sends ``connection.blocked`` when it is low on memory or disk and 234 ``connection.unblocked`` when the alarm clears. In between it stops reading 235 from the socket, so everything sent on that connection — a publish, a 236 declaration, a health probe — waits rather than failing. From outside that 237 is indistinguishable from a broker that has silently gone away, and the two 238 want opposite responses: a blocked broker is one to wait for, a dead one is 239 one to fail over from. 240 241 Separate from :class:`Transport` because not every transport has an answer. 242 A fake in a test is never blocked and can say so; a client library that 243 keeps the state to itself cannot be asked at all, which is what ``None`` 244 below is for. It is the same shape as Java's ``isBlocked()`` and 245 ``blockedReason()``, .NET's ``IsBlocked``/``BlockedReason`` and Go's 246 ``BlockedReason()``. 247 """ 248 249 @property 250 def blocked(self) -> bool | None: 251 """Whether the broker has blocked this connection. 252 253 ``None`` rather than ``False`` when the question could not be asked at 254 all — a client that does not expose the state, a connection that is 255 between reconnections. The two are different facts and a report that 256 folds them together says a broker is fine when nobody looked. 257 """ 258 259 @property 260 def blocked_reason(self) -> str | None: 261 """What the broker said when it blocked this connection. 262 263 ``None`` when it is not blocked, and also ``None`` when the client read 264 the reason and did not keep it — which is what aio-pika does, and why 265 this is separate from :attr:`blocked` rather than a string that is empty 266 when there is nothing to say. A reason invented here would read exactly 267 like one RabbitMQ sent. 268 """ 269 270 271@runtime_checkable 272class MessageSource(Protocol): 273 """A transport that can be asked for one message instead of subscribed to. 274 275 Separate from :class:`Transport` because it answers a question a 276 subscription cannot: *is there anything left*. A consumer is told when a 277 message arrives and never told that none will, so a tool that has to work 278 through what is on a queue and then stop — a replay, a drain, a one-off 279 inspection — cannot be built on one. 280 281 It is for tools rather than for services. Pulling one message at a time is a 282 round trip per message where a consumer gets a stream, so a service built on 283 this is a slow service. 284 """ 285 286 async def pull(self, queue: str) -> Delivery | None: 287 """Takes the message at the head of a queue, unsettled. 288 289 :param queue: what to read from 290 :returns: the delivery, or ``None`` when the queue has nothing waiting. 291 It comes back unsettled, so a caller that neither acknowledges nor 292 rejects it is holding it until the connection goes 293 """ 294 295 296@runtime_checkable 297class QueueAdmin(Protocol): 298 """A transport that can also report on and remove queues. 299 300 Separate from :class:`Transport` because not every transport has an answer: 301 "how many messages are waiting" means nothing to one that does not hold 302 them. Asking for it is therefore a question about the transport in hand, 303 which is why the connection checks before it asks. 304 """ 305 306 async def queue_exists(self, name: str) -> bool: 307 """Whether a queue is on the broker. 308 309 It must not create the queue. A declaration is the only thing AMQP 310 offers, and declaring a queue that is missing creates it — so anything 311 built on a declaration would create the very queues it was asked only to 312 look for. 313 """ 314 315 async def message_count(self, name: str) -> int: 316 """How many messages are waiting on a queue. 317 318 A number for a dashboard or a test, not a decision to make in a handler: 319 it is a snapshot of a queue that is still moving, and by the time it is 320 read somewhere else it is already wrong. 321 """ 322 323 async def delete_queue(self, name: str) -> None: 324 """Removes a queue and every message still on it. 325 326 For tests and for tools. A service that deletes queues is usually a 327 service that has confused a queue with a session. 328 """
36@dataclass(frozen=True, slots=True) 37class QueueSpec: 38 """How a queue should be declared. 39 40 :param durable: survives a broker restart 41 :param auto_delete: goes away when its last consumer does 42 :param exclusive: usable only by the connection that declared it 43 :param args: broker-specific arguments, such as ``x-dead-letter-exchange`` 44 """ 45 46 durable: bool = True 47 auto_delete: bool = False 48 exclusive: bool = False 49 args: Mapping[str, Any] = field(default_factory=dict)
How a queue should be declared.
Parameters
- durable: survives a broker restart
- auto_delete: goes away when its last consumer does
- exclusive: usable only by the connection that declared it
- args: broker-specific arguments, such as
x-dead-letter-exchange
52@dataclass(frozen=True, slots=True) 53class ExchangeSpec: 54 """How an exchange should be declared. 55 56 :param kind: ``direct``, ``topic``, ``fanout`` or ``headers`` 57 :param durable: survives a broker restart 58 :param auto_delete: goes away when its last binding does 59 :param args: broker-specific arguments 60 """ 61 62 kind: str = "topic" 63 durable: bool = True 64 auto_delete: bool = False 65 args: Mapping[str, Any] = field(default_factory=dict)
How an exchange should be declared.
Parameters
- kind:
direct,topic,fanoutorheaders - durable: survives a broker restart
- auto_delete: goes away when its last binding does
- args: broker-specific arguments
68@dataclass(frozen=True, slots=True) 69class ConsumeSpec: 70 """How a consumer should be set up. 71 72 :param prefetch: how many unacknowledged messages the broker will send at 73 once, or zero for the transport's default 74 :param tag: what to call this consumer to the broker, which is what an 75 operator sees when working out who is holding a message 76 :param args: broker-specific consumer arguments, such as the 77 ``x-stream-offset`` a stream consumer needs to say where it starts 78 """ 79 80 prefetch: int = 0 81 tag: str = "" 82 args: Mapping[str, Any] = field(default_factory=dict)
How a consumer should be set up.
Parameters
- prefetch: how many unacknowledged messages the broker will send at once, or zero for the transport's default
- tag: what to call this consumer to the broker, which is what an operator sees when working out who is holding a message
- args: broker-specific consumer arguments, such as the
x-stream-offseta stream consumer needs to say where it starts
85@dataclass(frozen=True, slots=True) 86class Outbound: 87 """A message on its way to the broker, already encoded. 88 89 :param body: the encoded payload 90 :param content_type: what the body is, for the consumer's codec 91 :param message_id: the AMQP message id, conventionally the envelope's 92 :param reply_to: AMQP's own ``reply-to`` property, naming where an answer 93 should be sent. Empty for the overwhelming majority of messages, which 94 are not questions. It is here as well as in the headers because the four 95 other libraries read the native property, and a Python responder that 96 only ever wrote a header could not be answered by a Java or .NET caller 97 :param headers: the envelope's headers plus the application's 98 :param persistent: asks the broker to write the message to disk, which is 99 not a guarantee on its own — a persistent message on a queue that is not 100 durable still dies with the broker 101 :param mandatory: asks the broker to return the message rather than drop it 102 when no queue is bound to receive it 103 """ 104 105 body: bytes 106 content_type: str = "" 107 message_id: str = "" 108 reply_to: str = "" 109 headers: Mapping[str, Any] = field(default_factory=dict) 110 persistent: bool = True 111 mandatory: bool = False
A message on its way to the broker, already encoded.
Parameters
- body: the encoded payload
- content_type: what the body is, for the consumer's codec
- message_id: the AMQP message id, conventionally the envelope's
- reply_to: AMQP's own
reply-toproperty, naming where an answer should be sent. Empty for the overwhelming majority of messages, which are not questions. It is here as well as in the headers because the four other libraries read the native property, and a Python responder that only ever wrote a header could not be answered by a Java or .NET caller - headers: the envelope's headers plus the application's
- persistent: asks the broker to write the message to disk, which is not a guarantee on its own — a persistent message on a queue that is not durable still dies with the broker
- mandatory: asks the broker to return the message rather than drop it when no queue is bound to receive it
114@dataclass(frozen=True, slots=True) 115class PublishResult: 116 """What the broker said about a published message. 117 118 :param message_id: the identifier it went out with 119 :param confirmed: the broker taking responsibility for the message. Without 120 publisher confirms this is false and nothing has been promised: the 121 message reached a socket, which is not the same thing 122 :param routed: false when a mandatory message reached no queue at all. A 123 message nothing is bound to receive is not an error to the broker; it is 124 dropped, silently, which is exactly the failure worth hearing about 125 :param return_reason: the broker's explanation when ``routed`` is false 126 """ 127 128 message_id: str = "" 129 confirmed: bool = False 130 routed: bool = True 131 return_reason: str = ""
What the broker said about a published message.
Parameters
- message_id: the identifier it went out with
- confirmed: the broker taking responsibility for the message. Without publisher confirms this is false and nothing has been promised: the message reached a socket, which is not the same thing
- routed: false when a mandatory message reached no queue at all. A message nothing is bound to receive is not an error to the broker; it is dropped, silently, which is exactly the failure worth hearing about
- return_reason: the broker's explanation when
routedis false
134@dataclass(frozen=True, slots=True) 135class Delivery: 136 """A message as it arrived, before any codec has looked at it. 137 138 :param body: the bytes, undecoded 139 :param content_type: what the sender said the body is, or ``None`` when the 140 sender said nothing — which is a different thing from an empty string 141 and is why the codecs distinguish them 142 :param routing_key: the key it arrived under 143 :param message_id: the AMQP message id 144 :param headers: everything the sender wrote, reserved names included 145 :param redelivered: the broker saying it has handed this message over 146 before. It is the broker's own view and says nothing about which attempt 147 this is: a requeue returns the bytes the broker was given, so the 148 attempt header still reads whatever the publisher wrote 149 :param ack: confirms the message and removes it from the queue 150 :param nack: returns the message, requeued or not 151 :param reply_to: AMQP's own ``reply-to`` property, empty when the sender did 152 not set one. A transport that cannot report it leaves it empty and the 153 responder falls back to the header, which is the only reason this has a 154 default at all 155 """ 156 157 body: bytes 158 content_type: str | None 159 routing_key: str 160 message_id: str 161 headers: Mapping[str, Any] 162 redelivered: bool 163 ack: Callable[[], Awaitable[None]] 164 nack: Callable[[bool], Awaitable[None]] 165 reply_to: str = ""
A message as it arrived, before any codec has looked at it.
Parameters
- body: the bytes, undecoded
- content_type: what the sender said the body is, or
Nonewhen the sender said nothing — which is a different thing from an empty string and is why the codecs distinguish them - routing_key: the key it arrived under
- message_id: the AMQP message id
- headers: everything the sender wrote, reserved names included
- redelivered: the broker saying it has handed this message over before. It is the broker's own view and says nothing about which attempt this is: a requeue returns the bytes the broker was given, so the attempt header still reads whatever the publisher wrote
- ack: confirms the message and removes it from the queue
- nack: returns the message, requeued or not
- reply_to: AMQP's own
reply-toproperty, empty when the sender did not set one. A transport that cannot report it leaves it empty and the responder falls back to the header, which is the only reason this has a default at all
168class Subscription(Protocol): 169 """A running consumer on a transport. 170 171 Stopping and releasing are two steps rather than one because a message that 172 has been delivered still has to be settled, and on AMQP a settlement travels 173 on the channel the delivery came in on. Closing that channel while a handler 174 is still working means acknowledging on a channel that has gone — which 175 RabbitMQ answers by closing the whole connection, taking down every other 176 consumer and publisher on it. 177 """ 178 179 async def stop(self) -> None: 180 """Stops delivery, leaving what has already arrived settleable. 181 182 It must not return until the transport has promised no further 183 deliveries, because that promise is what makes it safe for the consumer 184 above to shut its workers down. 185 """ 186 187 async def close(self) -> None: 188 """Releases what the subscription was holding. 189 190 Called once nothing is left unsettled. 191 """
A running consumer on a transport.
Stopping and releasing are two steps rather than one because a message that has been delivered still has to be settled, and on AMQP a settlement travels on the channel the delivery came in on. Closing that channel while a handler is still working means acknowledging on a channel that has gone — which RabbitMQ answers by closing the whole connection, taking down every other consumer and publisher on it.
1771def _no_init_or_replace_init(self, *args, **kwargs): 1772 cls = type(self) 1773 1774 if cls._is_protocol: 1775 raise TypeError('Protocols cannot be instantiated') 1776 1777 # Already using a custom `__init__`. No need to calculate correct 1778 # `__init__` to call. This can lead to RecursionError. See bpo-45121. 1779 if cls.__init__ is not _no_init_or_replace_init: 1780 return 1781 1782 # Initially, `__init__` of a protocol subclass is set to `_no_init_or_replace_init`. 1783 # The first instantiation of the subclass will call `_no_init_or_replace_init` which 1784 # searches for a proper new `__init__` in the MRO. The new `__init__` 1785 # replaces the subclass' old `__init__` (ie `_no_init_or_replace_init`). Subsequent 1786 # instantiation of the protocol subclass will thus use the new 1787 # `__init__` and no longer call `_no_init_or_replace_init`. 1788 for base in cls.__mro__: 1789 init = base.__dict__.get('__init__', _no_init_or_replace_init) 1790 if init is not _no_init_or_replace_init: 1791 cls.__init__ = init 1792 break 1793 else: 1794 # should not happen 1795 cls.__init__ = object.__init__ 1796 1797 cls.__init__(self, *args, **kwargs)
179 async def stop(self) -> None: 180 """Stops delivery, leaving what has already arrived settleable. 181 182 It must not return until the transport has promised no further 183 deliveries, because that promise is what makes it safe for the consumer 184 above to shut its workers down. 185 """
Stops delivery, leaving what has already arrived settleable.
It must not return until the transport has promised no further deliveries, because that promise is what makes it safe for the consumer above to shut its workers down.
194@runtime_checkable 195class Transport(Protocol): 196 """A connection to a broker. 197 198 Async because that is the shape a broker client has: every one of these is a 199 round trip, and a library that hid them behind blocking calls would make one 200 slow broker stall a whole process. The blocking API in 201 :mod:`acemq_amqp.sync` is built on top of this rather than beside it. 202 """ 203 204 async def declare_queue(self, name: str, spec: QueueSpec) -> None: 205 """Creates a queue if it is not already there.""" 206 207 async def declare_exchange(self, name: str, spec: ExchangeSpec) -> None: 208 """Creates an exchange if it is not already there.""" 209 210 async def bind(self, queue: str, exchange: str, routing_key: str) -> None: 211 """Routes messages matching a key from an exchange to a queue.""" 212 213 async def publish( 214 self, exchange: str, routing_key: str, message: Outbound 215 ) -> PublishResult: 216 """Sends one message and reports what the broker said about it.""" 217 218 async def consume( 219 self, 220 queue: str, 221 spec: ConsumeSpec, 222 deliver: Callable[[Delivery], Awaitable[None]], 223 ) -> Subscription: 224 """Delivers messages to ``deliver`` until the subscription is closed.""" 225 226 async def close(self) -> None: 227 """Releases the connection."""
A connection to a broker.
Async because that is the shape a broker client has: every one of these is a
round trip, and a library that hid them behind blocking calls would make one
slow broker stall a whole process. The blocking API in
acemq_amqp.sync is built on top of this rather than beside it.
1771def _no_init_or_replace_init(self, *args, **kwargs): 1772 cls = type(self) 1773 1774 if cls._is_protocol: 1775 raise TypeError('Protocols cannot be instantiated') 1776 1777 # Already using a custom `__init__`. No need to calculate correct 1778 # `__init__` to call. This can lead to RecursionError. See bpo-45121. 1779 if cls.__init__ is not _no_init_or_replace_init: 1780 return 1781 1782 # Initially, `__init__` of a protocol subclass is set to `_no_init_or_replace_init`. 1783 # The first instantiation of the subclass will call `_no_init_or_replace_init` which 1784 # searches for a proper new `__init__` in the MRO. The new `__init__` 1785 # replaces the subclass' old `__init__` (ie `_no_init_or_replace_init`). Subsequent 1786 # instantiation of the protocol subclass will thus use the new 1787 # `__init__` and no longer call `_no_init_or_replace_init`. 1788 for base in cls.__mro__: 1789 init = base.__dict__.get('__init__', _no_init_or_replace_init) 1790 if init is not _no_init_or_replace_init: 1791 cls.__init__ = init 1792 break 1793 else: 1794 # should not happen 1795 cls.__init__ = object.__init__ 1796 1797 cls.__init__(self, *args, **kwargs)
204 async def declare_queue(self, name: str, spec: QueueSpec) -> None: 205 """Creates a queue if it is not already there."""
Creates a queue if it is not already there.
207 async def declare_exchange(self, name: str, spec: ExchangeSpec) -> None: 208 """Creates an exchange if it is not already there."""
Creates an exchange if it is not already there.
210 async def bind(self, queue: str, exchange: str, routing_key: str) -> None: 211 """Routes messages matching a key from an exchange to a queue."""
Routes messages matching a key from an exchange to a queue.
213 async def publish( 214 self, exchange: str, routing_key: str, message: Outbound 215 ) -> PublishResult: 216 """Sends one message and reports what the broker said about it."""
Sends one message and reports what the broker said about it.
218 async def consume( 219 self, 220 queue: str, 221 spec: ConsumeSpec, 222 deliver: Callable[[Delivery], Awaitable[None]], 223 ) -> Subscription: 224 """Delivers messages to ``deliver`` until the subscription is closed."""
Delivers messages to deliver until the subscription is closed.
230@runtime_checkable 231class BlockedState(Protocol): 232 """A transport that can say whether the broker has blocked its connection. 233 234 RabbitMQ sends ``connection.blocked`` when it is low on memory or disk and 235 ``connection.unblocked`` when the alarm clears. In between it stops reading 236 from the socket, so everything sent on that connection — a publish, a 237 declaration, a health probe — waits rather than failing. From outside that 238 is indistinguishable from a broker that has silently gone away, and the two 239 want opposite responses: a blocked broker is one to wait for, a dead one is 240 one to fail over from. 241 242 Separate from :class:`Transport` because not every transport has an answer. 243 A fake in a test is never blocked and can say so; a client library that 244 keeps the state to itself cannot be asked at all, which is what ``None`` 245 below is for. It is the same shape as Java's ``isBlocked()`` and 246 ``blockedReason()``, .NET's ``IsBlocked``/``BlockedReason`` and Go's 247 ``BlockedReason()``. 248 """ 249 250 @property 251 def blocked(self) -> bool | None: 252 """Whether the broker has blocked this connection. 253 254 ``None`` rather than ``False`` when the question could not be asked at 255 all — a client that does not expose the state, a connection that is 256 between reconnections. The two are different facts and a report that 257 folds them together says a broker is fine when nobody looked. 258 """ 259 260 @property 261 def blocked_reason(self) -> str | None: 262 """What the broker said when it blocked this connection. 263 264 ``None`` when it is not blocked, and also ``None`` when the client read 265 the reason and did not keep it — which is what aio-pika does, and why 266 this is separate from :attr:`blocked` rather than a string that is empty 267 when there is nothing to say. A reason invented here would read exactly 268 like one RabbitMQ sent. 269 """
A transport that can say whether the broker has blocked its connection.
RabbitMQ sends connection.blocked when it is low on memory or disk and
connection.unblocked when the alarm clears. In between it stops reading
from the socket, so everything sent on that connection — a publish, a
declaration, a health probe — waits rather than failing. From outside that
is indistinguishable from a broker that has silently gone away, and the two
want opposite responses: a blocked broker is one to wait for, a dead one is
one to fail over from.
Separate from Transport because not every transport has an answer.
A fake in a test is never blocked and can say so; a client library that
keeps the state to itself cannot be asked at all, which is what None
below is for. It is the same shape as Java's isBlocked() and
blockedReason(), .NET's IsBlocked/BlockedReason and Go's
BlockedReason().
1771def _no_init_or_replace_init(self, *args, **kwargs): 1772 cls = type(self) 1773 1774 if cls._is_protocol: 1775 raise TypeError('Protocols cannot be instantiated') 1776 1777 # Already using a custom `__init__`. No need to calculate correct 1778 # `__init__` to call. This can lead to RecursionError. See bpo-45121. 1779 if cls.__init__ is not _no_init_or_replace_init: 1780 return 1781 1782 # Initially, `__init__` of a protocol subclass is set to `_no_init_or_replace_init`. 1783 # The first instantiation of the subclass will call `_no_init_or_replace_init` which 1784 # searches for a proper new `__init__` in the MRO. The new `__init__` 1785 # replaces the subclass' old `__init__` (ie `_no_init_or_replace_init`). Subsequent 1786 # instantiation of the protocol subclass will thus use the new 1787 # `__init__` and no longer call `_no_init_or_replace_init`. 1788 for base in cls.__mro__: 1789 init = base.__dict__.get('__init__', _no_init_or_replace_init) 1790 if init is not _no_init_or_replace_init: 1791 cls.__init__ = init 1792 break 1793 else: 1794 # should not happen 1795 cls.__init__ = object.__init__ 1796 1797 cls.__init__(self, *args, **kwargs)
250 @property 251 def blocked(self) -> bool | None: 252 """Whether the broker has blocked this connection. 253 254 ``None`` rather than ``False`` when the question could not be asked at 255 all — a client that does not expose the state, a connection that is 256 between reconnections. The two are different facts and a report that 257 folds them together says a broker is fine when nobody looked. 258 """
Whether the broker has blocked this connection.
None rather than False when the question could not be asked at
all — a client that does not expose the state, a connection that is
between reconnections. The two are different facts and a report that
folds them together says a broker is fine when nobody looked.
260 @property 261 def blocked_reason(self) -> str | None: 262 """What the broker said when it blocked this connection. 263 264 ``None`` when it is not blocked, and also ``None`` when the client read 265 the reason and did not keep it — which is what aio-pika does, and why 266 this is separate from :attr:`blocked` rather than a string that is empty 267 when there is nothing to say. A reason invented here would read exactly 268 like one RabbitMQ sent. 269 """
What the broker said when it blocked this connection.
None when it is not blocked, and also None when the client read
the reason and did not keep it — which is what aio-pika does, and why
this is separate from blocked rather than a string that is empty
when there is nothing to say. A reason invented here would read exactly
like one RabbitMQ sent.
272@runtime_checkable 273class MessageSource(Protocol): 274 """A transport that can be asked for one message instead of subscribed to. 275 276 Separate from :class:`Transport` because it answers a question a 277 subscription cannot: *is there anything left*. A consumer is told when a 278 message arrives and never told that none will, so a tool that has to work 279 through what is on a queue and then stop — a replay, a drain, a one-off 280 inspection — cannot be built on one. 281 282 It is for tools rather than for services. Pulling one message at a time is a 283 round trip per message where a consumer gets a stream, so a service built on 284 this is a slow service. 285 """ 286 287 async def pull(self, queue: str) -> Delivery | None: 288 """Takes the message at the head of a queue, unsettled. 289 290 :param queue: what to read from 291 :returns: the delivery, or ``None`` when the queue has nothing waiting. 292 It comes back unsettled, so a caller that neither acknowledges nor 293 rejects it is holding it until the connection goes 294 """
A transport that can be asked for one message instead of subscribed to.
Separate from Transport because it answers a question a
subscription cannot: is there anything left. A consumer is told when a
message arrives and never told that none will, so a tool that has to work
through what is on a queue and then stop — a replay, a drain, a one-off
inspection — cannot be built on one.
It is for tools rather than for services. Pulling one message at a time is a round trip per message where a consumer gets a stream, so a service built on this is a slow service.
1771def _no_init_or_replace_init(self, *args, **kwargs): 1772 cls = type(self) 1773 1774 if cls._is_protocol: 1775 raise TypeError('Protocols cannot be instantiated') 1776 1777 # Already using a custom `__init__`. No need to calculate correct 1778 # `__init__` to call. This can lead to RecursionError. See bpo-45121. 1779 if cls.__init__ is not _no_init_or_replace_init: 1780 return 1781 1782 # Initially, `__init__` of a protocol subclass is set to `_no_init_or_replace_init`. 1783 # The first instantiation of the subclass will call `_no_init_or_replace_init` which 1784 # searches for a proper new `__init__` in the MRO. The new `__init__` 1785 # replaces the subclass' old `__init__` (ie `_no_init_or_replace_init`). Subsequent 1786 # instantiation of the protocol subclass will thus use the new 1787 # `__init__` and no longer call `_no_init_or_replace_init`. 1788 for base in cls.__mro__: 1789 init = base.__dict__.get('__init__', _no_init_or_replace_init) 1790 if init is not _no_init_or_replace_init: 1791 cls.__init__ = init 1792 break 1793 else: 1794 # should not happen 1795 cls.__init__ = object.__init__ 1796 1797 cls.__init__(self, *args, **kwargs)
287 async def pull(self, queue: str) -> Delivery | None: 288 """Takes the message at the head of a queue, unsettled. 289 290 :param queue: what to read from 291 :returns: the delivery, or ``None`` when the queue has nothing waiting. 292 It comes back unsettled, so a caller that neither acknowledges nor 293 rejects it is holding it until the connection goes 294 """
Takes the message at the head of a queue, unsettled.
Parameters
- queue: what to read from
:returns: the delivery, or
Nonewhen the queue has nothing waiting. It comes back unsettled, so a caller that neither acknowledges nor rejects it is holding it until the connection goes
297@runtime_checkable 298class QueueAdmin(Protocol): 299 """A transport that can also report on and remove queues. 300 301 Separate from :class:`Transport` because not every transport has an answer: 302 "how many messages are waiting" means nothing to one that does not hold 303 them. Asking for it is therefore a question about the transport in hand, 304 which is why the connection checks before it asks. 305 """ 306 307 async def queue_exists(self, name: str) -> bool: 308 """Whether a queue is on the broker. 309 310 It must not create the queue. A declaration is the only thing AMQP 311 offers, and declaring a queue that is missing creates it — so anything 312 built on a declaration would create the very queues it was asked only to 313 look for. 314 """ 315 316 async def message_count(self, name: str) -> int: 317 """How many messages are waiting on a queue. 318 319 A number for a dashboard or a test, not a decision to make in a handler: 320 it is a snapshot of a queue that is still moving, and by the time it is 321 read somewhere else it is already wrong. 322 """ 323 324 async def delete_queue(self, name: str) -> None: 325 """Removes a queue and every message still on it. 326 327 For tests and for tools. A service that deletes queues is usually a 328 service that has confused a queue with a session. 329 """
A transport that can also report on and remove queues.
Separate from Transport because not every transport has an answer:
"how many messages are waiting" means nothing to one that does not hold
them. Asking for it is therefore a question about the transport in hand,
which is why the connection checks before it asks.
1771def _no_init_or_replace_init(self, *args, **kwargs): 1772 cls = type(self) 1773 1774 if cls._is_protocol: 1775 raise TypeError('Protocols cannot be instantiated') 1776 1777 # Already using a custom `__init__`. No need to calculate correct 1778 # `__init__` to call. This can lead to RecursionError. See bpo-45121. 1779 if cls.__init__ is not _no_init_or_replace_init: 1780 return 1781 1782 # Initially, `__init__` of a protocol subclass is set to `_no_init_or_replace_init`. 1783 # The first instantiation of the subclass will call `_no_init_or_replace_init` which 1784 # searches for a proper new `__init__` in the MRO. The new `__init__` 1785 # replaces the subclass' old `__init__` (ie `_no_init_or_replace_init`). Subsequent 1786 # instantiation of the protocol subclass will thus use the new 1787 # `__init__` and no longer call `_no_init_or_replace_init`. 1788 for base in cls.__mro__: 1789 init = base.__dict__.get('__init__', _no_init_or_replace_init) 1790 if init is not _no_init_or_replace_init: 1791 cls.__init__ = init 1792 break 1793 else: 1794 # should not happen 1795 cls.__init__ = object.__init__ 1796 1797 cls.__init__(self, *args, **kwargs)
307 async def queue_exists(self, name: str) -> bool: 308 """Whether a queue is on the broker. 309 310 It must not create the queue. A declaration is the only thing AMQP 311 offers, and declaring a queue that is missing creates it — so anything 312 built on a declaration would create the very queues it was asked only to 313 look for. 314 """
Whether a queue is on the broker.
It must not create the queue. A declaration is the only thing AMQP offers, and declaring a queue that is missing creates it — so anything built on a declaration would create the very queues it was asked only to look for.
316 async def message_count(self, name: str) -> int: 317 """How many messages are waiting on a queue. 318 319 A number for a dashboard or a test, not a decision to make in a handler: 320 it is a snapshot of a queue that is still moving, and by the time it is 321 read somewhere else it is already wrong. 322 """
How many messages are waiting on a queue.
A number for a dashboard or a test, not a decision to make in a handler: it is a snapshot of a queue that is still moving, and by the time it is read somewhere else it is already wrong.
324 async def delete_queue(self, name: str) -> None: 325 """Removes a queue and every message still on it. 326 327 For tests and for tools. A service that deletes queues is usually a 328 service that has confused a queue with a session. 329 """
Removes a queue and every message still on it.
For tests and for tools. A service that deletes queues is usually a service that has confused a queue with a session.