amqp-client-python 0.2.0__py3-none-any.whl → 0.2.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- amqp_client_python/__init__.py +11 -0
- amqp_client_python/domain/models/__init__.py +6 -0
- amqp_client_python/domain/models/config.py +5 -12
- amqp_client_python/domain/models/options.py +1 -5
- amqp_client_python/domain/models/ssl_options.py +1 -6
- amqp_client_python/domain/utils/__init__.py +5 -0
- amqp_client_python/event/__init__.py +9 -0
- amqp_client_python/exceptions/__init__.py +12 -0
- amqp_client_python/rabbitmq/__init__.py +15 -1
- amqp_client_python/rabbitmq/async_channel.py +74 -30
- amqp_client_python/rabbitmq/async_connection.py +293 -41
- amqp_client_python/rabbitmq/async_eventbus_rabbitmq.py +87 -43
- amqp_client_python/rabbitmq/channel_rabbitmq.py +26 -14
- amqp_client_python/rabbitmq/connection_rabbitmq.py +5 -5
- amqp_client_python/rabbitmq/eventbus_rabbitmq.py +4 -4
- amqp_client_python/rabbitmq/eventbus_wrapper_rabbitmq.py +29 -3
- {amqp_client_python-0.2.0.dist-info → amqp_client_python-0.2.1.dist-info}/METADATA +50 -18
- {amqp_client_python-0.2.0.dist-info → amqp_client_python-0.2.1.dist-info}/RECORD +22 -22
- {amqp_client_python-0.2.0.dist-info → amqp_client_python-0.2.1.dist-info}/WHEEL +1 -1
- {amqp_client_python-0.2.0.dist-info → amqp_client_python-0.2.1.dist-info/licenses}/LICENSE +0 -0
amqp_client_python/__init__.py
CHANGED
|
@@ -2,3 +2,14 @@ from .rabbitmq import EventbusRabbitMQ, AsyncEventbusRabbitMQ, EventbusWrapperRa
|
|
|
2
2
|
from .domain.models import Config, Options, SSLOptions
|
|
3
3
|
from .domain.utils import ConnectionType
|
|
4
4
|
from pika import DeliveryMode
|
|
5
|
+
|
|
6
|
+
__all__ = [
|
|
7
|
+
"EventbusRabbitMQ",
|
|
8
|
+
"AsyncEventbusRabbitMQ",
|
|
9
|
+
"EventbusWrapperRabbitMQ",
|
|
10
|
+
"Config",
|
|
11
|
+
"Options",
|
|
12
|
+
"SSLOptions",
|
|
13
|
+
"ConnectionType",
|
|
14
|
+
"DeliveryMode",
|
|
15
|
+
]
|
|
@@ -10,15 +10,11 @@ class Config:
|
|
|
10
10
|
self, options: Options, ssl_options: Optional[SSLOptions] = None
|
|
11
11
|
) -> None:
|
|
12
12
|
"""
|
|
13
|
-
Create
|
|
13
|
+
Create a Config object that holds and manages the connection information.
|
|
14
14
|
|
|
15
15
|
Args:
|
|
16
|
-
options:
|
|
17
|
-
ssl_options:
|
|
18
|
-
|
|
19
|
-
Returns:
|
|
20
|
-
|
|
21
|
-
Raises:
|
|
16
|
+
options: holds information for establishing connection
|
|
17
|
+
ssl_options: holds information for establishing SSL connections
|
|
22
18
|
|
|
23
19
|
Examples:
|
|
24
20
|
>>> Config(
|
|
@@ -32,16 +28,13 @@ class Config:
|
|
|
32
28
|
|
|
33
29
|
def build(self) -> "Config":
|
|
34
30
|
"""
|
|
35
|
-
Create
|
|
36
|
-
|
|
37
|
-
Args:
|
|
31
|
+
Create a Config object that holds and manages the connection information.
|
|
38
32
|
|
|
39
33
|
Returns:
|
|
40
34
|
Config object
|
|
41
35
|
|
|
42
|
-
Raises:
|
|
43
|
-
|
|
44
36
|
Examples:
|
|
37
|
+
>>> # config = Config(...)
|
|
45
38
|
>>> config.build()
|
|
46
39
|
"""
|
|
47
40
|
opt = {
|
|
@@ -18,7 +18,7 @@ class Options:
|
|
|
18
18
|
**kwargs: Dict[str, Any]
|
|
19
19
|
) -> None:
|
|
20
20
|
"""
|
|
21
|
-
Create an Options object that
|
|
21
|
+
Create an Options object that holds the credentials and configuration options.
|
|
22
22
|
|
|
23
23
|
Args:
|
|
24
24
|
queue_name: name of queue that will be used for subscriptions
|
|
@@ -34,10 +34,6 @@ class Options:
|
|
|
34
34
|
publisher_confirms: wait for publisher confirmations
|
|
35
35
|
**kwargs: pika options
|
|
36
36
|
|
|
37
|
-
Returns:
|
|
38
|
-
|
|
39
|
-
Raises:
|
|
40
|
-
|
|
41
37
|
Examples:
|
|
42
38
|
>>> Options("example", "example.rpc", "example.rpc", "amqp://admin:admin@localhost:5672/")
|
|
43
39
|
|
|
@@ -3,18 +3,13 @@ class SSLOptions:
|
|
|
3
3
|
self, certfile_path: str, keyfile_path: str, ca_certs_path: str
|
|
4
4
|
) -> None:
|
|
5
5
|
"""
|
|
6
|
-
Create an SslOptions object that
|
|
6
|
+
Create an SslOptions object that holds the cert paths.
|
|
7
7
|
|
|
8
8
|
Args:
|
|
9
9
|
certfile_path: cert file path string
|
|
10
10
|
keyfile_path: private key file path string
|
|
11
11
|
ca_certs_path: ca file path string
|
|
12
12
|
|
|
13
|
-
Returns:
|
|
14
|
-
Um dicionário com as notas da escala e os graus.
|
|
15
|
-
|
|
16
|
-
Raises:
|
|
17
|
-
|
|
18
13
|
Examples:
|
|
19
14
|
>>> SSLOptions("./.certs/cert.pem", "./.certs/privkey.pem", "./.certs/ca.pem")
|
|
20
15
|
"""
|
|
@@ -4,3 +4,12 @@ from .async_provider_handler import AsyncProviderHandler
|
|
|
4
4
|
from .async_subscriber_handler import AsyncSubscriberHandler
|
|
5
5
|
from .provider_handler import ProviderHandler
|
|
6
6
|
from .subscriber_handler import SubscriberHandler
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
"IntegrationEvent",
|
|
10
|
+
"IntegrationEventHandler",
|
|
11
|
+
"AsyncProviderHandler",
|
|
12
|
+
"AsyncSubscriberHandler",
|
|
13
|
+
"ProviderHandler",
|
|
14
|
+
"SubscriberHandler",
|
|
15
|
+
]
|
|
@@ -7,3 +7,15 @@ from .publish_timeout_exception import PublishTimeoutException
|
|
|
7
7
|
from .auto_reconnect_exception import AutoReconnectException
|
|
8
8
|
from .blocking_exception import BlockingException
|
|
9
9
|
from .thread_unsafe_exception import ThreadUnsafeException
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"EventBusException",
|
|
13
|
+
"NackException",
|
|
14
|
+
"RpcProviderException",
|
|
15
|
+
"TimeoutException",
|
|
16
|
+
"ResponseTimeoutException",
|
|
17
|
+
"PublishTimeoutException",
|
|
18
|
+
"AutoReconnectException",
|
|
19
|
+
"BlockingException",
|
|
20
|
+
"ThreadUnsafeException",
|
|
21
|
+
]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from .async_eventbus_rabbitmq import AsyncEventbusRabbitMQ
|
|
2
2
|
from .async_connection_factory import AsyncConnectionFactoryRabbitMQ
|
|
3
|
-
from .async_connection import AsyncConnection
|
|
3
|
+
from .async_connection import AsyncConnection
|
|
4
4
|
from .async_channel_factory import AsyncChannelFactoryRabbitMQ
|
|
5
5
|
from .async_channel import AsyncChannel
|
|
6
6
|
from .eventbus_rabbitmq import EventbusRabbitMQ
|
|
@@ -9,3 +9,17 @@ from .connection_rabbitmq import ConnectionRabbitMQ
|
|
|
9
9
|
from .channel_factory_rabbitmq import ChannelFactoryRabbitMQ
|
|
10
10
|
from .channel_rabbitmq import ChannelRabbitMQ
|
|
11
11
|
from .eventbus_wrapper_rabbitmq import EventbusWrapperRabbitMQ
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"AsyncEventbusRabbitMQ",
|
|
15
|
+
"AsyncConnectionFactoryRabbitMQ",
|
|
16
|
+
"AsyncConnection",
|
|
17
|
+
"AsyncChannelFactoryRabbitMQ",
|
|
18
|
+
"AsyncChannel",
|
|
19
|
+
"EventbusRabbitMQ",
|
|
20
|
+
"ConnectionFactoryRabbitMQ",
|
|
21
|
+
"ConnectionRabbitMQ",
|
|
22
|
+
"ChannelFactoryRabbitMQ",
|
|
23
|
+
"ChannelRabbitMQ",
|
|
24
|
+
"EventbusWrapperRabbitMQ",
|
|
25
|
+
]
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
from collections import deque
|
|
1
2
|
from typing import MutableMapping, Mapping, Optional, Union, Dict, List
|
|
2
3
|
from .async_channel_factory import AsyncChannelFactoryRabbitMQ
|
|
3
4
|
from amqp_client_python.exceptions import (
|
|
@@ -59,13 +60,19 @@ class AsyncChannel:
|
|
|
59
60
|
self._deliveries: MutableMapping[int, Future] = {}
|
|
60
61
|
self.response_timeout = 60
|
|
61
62
|
|
|
63
|
+
@staticmethod
|
|
64
|
+
def _serialize_payload(body, content_type: str = "application/json"):
|
|
65
|
+
if isinstance(body, (bytes, bytearray, memoryview)):
|
|
66
|
+
return body
|
|
67
|
+
return dumps(body)
|
|
68
|
+
|
|
62
69
|
@property
|
|
63
70
|
def is_open(self) -> Optional[bool]:
|
|
64
71
|
return self._channel and self._channel.is_open
|
|
65
72
|
|
|
66
|
-
def open(self, connection: AsyncioConnection, callbacks=
|
|
73
|
+
def open(self, connection: AsyncioConnection, callbacks=None):
|
|
67
74
|
if not self.is_open:
|
|
68
|
-
self.callbacks = callbacks
|
|
75
|
+
self.callbacks = callbacks if callbacks is not None else deque()
|
|
69
76
|
self._connection = connection
|
|
70
77
|
self.ioloop = connection.ioloop
|
|
71
78
|
self._channel = self.channel_factory.create_channel(
|
|
@@ -87,10 +94,15 @@ class AsyncChannel:
|
|
|
87
94
|
self.ioloop.create_task(self.process_callbacks()) # type: ignore
|
|
88
95
|
|
|
89
96
|
async def process_callbacks(self):
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
97
|
+
while self.callbacks:
|
|
98
|
+
callback, future = self.callbacks.popleft()
|
|
99
|
+
try:
|
|
100
|
+
result = await callback()
|
|
101
|
+
if not future.done():
|
|
102
|
+
future.set_result(result)
|
|
103
|
+
except Exception as exc:
|
|
104
|
+
if not future.done():
|
|
105
|
+
future.set_exception(exc)
|
|
94
106
|
|
|
95
107
|
def add_on_channel_close_callback(self):
|
|
96
108
|
"""This method tells pika to call the on_channel_closed method if
|
|
@@ -108,7 +120,6 @@ class AsyncChannel:
|
|
|
108
120
|
:param pika.channel.Channel: The closed channel
|
|
109
121
|
:param Exception reason: why the channel was closed
|
|
110
122
|
"""
|
|
111
|
-
print("channel closed:", reason, flush=True)
|
|
112
123
|
LOGGER.info(f"Channel {channel} was closed: {reason}")
|
|
113
124
|
self._consuming = False
|
|
114
125
|
if self._connection.is_closing or self._connection.is_closed:
|
|
@@ -238,10 +249,10 @@ class AsyncChannel:
|
|
|
238
249
|
)
|
|
239
250
|
else:
|
|
240
251
|
future.set_result(body)
|
|
241
|
-
return not self.auto_ack and self._channel_rpc.basic_ack(
|
|
252
|
+
return not self.auto_ack and self._channel_rpc.is_open and self._channel_rpc.basic_ack(
|
|
242
253
|
basic_deliver.delivery_tag
|
|
243
254
|
)
|
|
244
|
-
not self.auto_ack and self._channel_rpc.basic_nack(
|
|
255
|
+
not self.auto_ack and self._channel_rpc.is_open and self._channel_rpc.basic_nack(
|
|
245
256
|
basic_deliver.delivery_tag, requeue=False
|
|
246
257
|
)
|
|
247
258
|
|
|
@@ -303,7 +314,7 @@ class AsyncChannel:
|
|
|
303
314
|
if self.rpc_publisher_started:
|
|
304
315
|
return True
|
|
305
316
|
if self.rpc_publisher_starting:
|
|
306
|
-
return await self.
|
|
317
|
+
return await self.rpc_publisher_future
|
|
307
318
|
self.rpc_publisher_starting = True
|
|
308
319
|
self.rpc_publisher_future = self.ioloop.create_future()
|
|
309
320
|
|
|
@@ -334,34 +345,41 @@ class AsyncChannel:
|
|
|
334
345
|
routing_key: str,
|
|
335
346
|
body,
|
|
336
347
|
content_type,
|
|
337
|
-
|
|
348
|
+
response_timeout,
|
|
338
349
|
delivery_mode=DeliveryMode.Transient,
|
|
339
350
|
expiration: Optional[Union[str, None]] = None,
|
|
340
351
|
**key_args,
|
|
341
352
|
):
|
|
353
|
+
# Create future to track RPC response
|
|
342
354
|
future = self.ioloop.create_future() # type: ignore
|
|
355
|
+
# Generate unique correlation ID for RPC matching
|
|
343
356
|
corr_id = str(uuid4())
|
|
357
|
+
# Store future in tracking dictionary
|
|
344
358
|
self.rpc_futures[corr_id] = {"response": future}
|
|
359
|
+
# Setup cleanup callback for completed RPC
|
|
345
360
|
clean_response = partial(self.clean_rpc_response, corr_id)
|
|
346
361
|
future.add_done_callback(clean_response)
|
|
347
362
|
|
|
363
|
+
# Ensure RPC response consumer is operational
|
|
348
364
|
await self.start_rpc_consumer()
|
|
349
365
|
|
|
366
|
+
# Publish RPC request with metadata headers
|
|
350
367
|
self._channel.basic_publish(
|
|
351
368
|
exchange_name,
|
|
352
369
|
routing_key,
|
|
353
|
-
|
|
370
|
+
self._serialize_payload(body, content_type),
|
|
354
371
|
properties=BasicProperties(
|
|
355
|
-
reply_to=self._callback_queue,
|
|
356
|
-
correlation_id=corr_id,
|
|
357
|
-
content_type=content_type,
|
|
358
|
-
delivery_mode=delivery_mode,
|
|
359
|
-
expiration=expiration,
|
|
372
|
+
reply_to=self._callback_queue, # Response queue address
|
|
373
|
+
correlation_id=corr_id, # Request/response matching ID
|
|
374
|
+
content_type=content_type, # Payload format descriptor
|
|
375
|
+
delivery_mode=delivery_mode, # Persistent/Transient delivery
|
|
376
|
+
expiration=expiration, # Message TTL control
|
|
360
377
|
**key_args,
|
|
361
378
|
),
|
|
362
379
|
mandatory=False,
|
|
363
380
|
)
|
|
364
381
|
|
|
382
|
+
# Setup timeout handler for unfulfilled requests
|
|
365
383
|
def not_arrived(id: str):
|
|
366
384
|
if id in self.rpc_futures:
|
|
367
385
|
future = self.rpc_futures[id]
|
|
@@ -375,27 +393,40 @@ class AsyncChannel:
|
|
|
375
393
|
)
|
|
376
394
|
|
|
377
395
|
func = partial(not_arrived, corr_id)
|
|
378
|
-
|
|
396
|
+
# Schedule timeout check using event loop
|
|
397
|
+
self.ioloop.call_later(response_timeout, func) # type: ignore
|
|
379
398
|
|
|
399
|
+
# Handle publisher confirms if enabled
|
|
380
400
|
if self.publisher_confirms:
|
|
381
401
|
return await self.handle_publish(future, corr_id)
|
|
402
|
+
# Return final response or exception
|
|
382
403
|
return await future
|
|
383
404
|
|
|
384
405
|
async def handle_publish(self, future, corr_id):
|
|
406
|
+
"""Manages dual confirmation flow (publish + response)"""
|
|
407
|
+
|
|
408
|
+
# Create separate tracking for publish confirmation
|
|
385
409
|
publish_future = self.ioloop.create_future()
|
|
410
|
+
# Store both futures in tracking system
|
|
386
411
|
self.rpc_futures[corr_id]["published"] = publish_future
|
|
412
|
+
# Initiate confirmation tracking
|
|
387
413
|
self.publish_confirmation(publish_future)
|
|
414
|
+
# Concurrently wait for first completed operation:
|
|
415
|
+
# - Publisher confirmation (ACK/NACK)
|
|
416
|
+
# - RPC response arrival
|
|
388
417
|
done_all, pending_all = await wait(
|
|
389
418
|
[publish_future, future], return_when=FIRST_COMPLETED
|
|
390
419
|
)
|
|
420
|
+
# Extract potential exception from completed operation
|
|
391
421
|
exception = done_all.pop().exception()
|
|
392
422
|
if exception:
|
|
423
|
+
# Cancel remaining operations
|
|
393
424
|
[undone.cancel() for undone in pending_all]
|
|
394
425
|
raise exception
|
|
395
|
-
elif len(pending_all):
|
|
426
|
+
elif len(pending_all): # Response still pending after confirmation
|
|
396
427
|
return await future
|
|
397
428
|
else:
|
|
398
|
-
return future.result()
|
|
429
|
+
return future.result() # Return final result directly
|
|
399
430
|
|
|
400
431
|
async def publish(
|
|
401
432
|
self,
|
|
@@ -411,7 +442,7 @@ class AsyncChannel:
|
|
|
411
442
|
self._channel.basic_publish(
|
|
412
443
|
exchange_name,
|
|
413
444
|
routing_key,
|
|
414
|
-
|
|
445
|
+
self._serialize_payload(body, content_type),
|
|
415
446
|
properties=BasicProperties(
|
|
416
447
|
reply_to=self._callback_queue,
|
|
417
448
|
content_type=content_type,
|
|
@@ -436,9 +467,15 @@ class AsyncChannel:
|
|
|
436
467
|
return await publish_future
|
|
437
468
|
|
|
438
469
|
def publish_confirmation(self, future: Future):
|
|
470
|
+
"""Tracks message publishes requiring confirmation"""
|
|
471
|
+
|
|
472
|
+
# Increment sequence counter for message tracking
|
|
439
473
|
self._message_number += 1
|
|
474
|
+
# Store future reference for confirmation handling
|
|
440
475
|
self.futures[self._message_number] = future
|
|
476
|
+
# Link to delivery tracking system
|
|
441
477
|
self._deliveries[self._message_number] = future
|
|
478
|
+
# Register cleanup callback using partial binding
|
|
442
479
|
clean = partial(self.clean_publish_confirmation, self._message_number)
|
|
443
480
|
future.add_done_callback(clean)
|
|
444
481
|
|
|
@@ -455,18 +492,19 @@ class AsyncChannel:
|
|
|
455
492
|
routing_key: str,
|
|
456
493
|
queue_name: str,
|
|
457
494
|
callback,
|
|
458
|
-
|
|
495
|
+
timeout,
|
|
459
496
|
content_type="application/json",
|
|
460
497
|
exchange_type="topic",
|
|
461
498
|
durable=True,
|
|
462
499
|
auto_delete=False,
|
|
500
|
+
auto_decode=True,
|
|
463
501
|
):
|
|
464
502
|
await self.start_rpc_publisher()
|
|
465
503
|
self.register_queue(
|
|
466
504
|
exchange_name, queue_name, exchange_type, durable, auto_delete
|
|
467
505
|
)
|
|
468
506
|
self.register_handler(
|
|
469
|
-
queue_name, routing_key, callback, content_type,
|
|
507
|
+
queue_name, routing_key, callback, content_type, timeout, auto_decode=auto_decode
|
|
470
508
|
)
|
|
471
509
|
|
|
472
510
|
self.queue_bind(queue_name, exchange_name, routing_key)
|
|
@@ -501,13 +539,14 @@ class AsyncChannel:
|
|
|
501
539
|
self.consumers[queue_name] = False
|
|
502
540
|
|
|
503
541
|
def register_handler(
|
|
504
|
-
self, queue_name, routing_key, callback, content_type, response_timeout
|
|
542
|
+
self, queue_name, routing_key, callback, content_type, response_timeout, auto_decode=True
|
|
505
543
|
):
|
|
506
544
|
if routing_key not in self.subscribes[queue_name]:
|
|
507
545
|
self.subscribes[queue_name][routing_key] = {
|
|
508
546
|
"handle": callback,
|
|
509
547
|
"content_type": content_type,
|
|
510
548
|
"response_timeout": response_timeout or self.response_timeout,
|
|
549
|
+
"auto_decode": auto_decode,
|
|
511
550
|
}
|
|
512
551
|
if "*" in routing_key and routing_key not in self.re_subscribes:
|
|
513
552
|
self.re_subscribes.append(routing_key)
|
|
@@ -518,17 +557,18 @@ class AsyncChannel:
|
|
|
518
557
|
routing_key: str,
|
|
519
558
|
queue_name: str,
|
|
520
559
|
callback,
|
|
521
|
-
|
|
560
|
+
timeout: Optional[int],
|
|
522
561
|
content_type: str = "application/json",
|
|
523
562
|
exchange_type: str = "topic",
|
|
524
563
|
durable: bool = True,
|
|
525
564
|
auto_delete: bool = False,
|
|
565
|
+
auto_decode: bool = True,
|
|
526
566
|
) -> None:
|
|
527
567
|
self.register_queue(
|
|
528
568
|
exchange_name, queue_name, exchange_type, durable, auto_delete
|
|
529
569
|
)
|
|
530
570
|
self.register_handler(
|
|
531
|
-
queue_name, routing_key, callback, content_type,
|
|
571
|
+
queue_name, routing_key, callback, content_type, timeout, auto_decode=auto_decode
|
|
532
572
|
)
|
|
533
573
|
|
|
534
574
|
self.queue_bind(queue_name, exchange_name, routing_key)
|
|
@@ -556,7 +596,11 @@ class AsyncChannel:
|
|
|
556
596
|
|
|
557
597
|
async def process_message(handler: HandlerType):
|
|
558
598
|
nonlocal body
|
|
559
|
-
|
|
599
|
+
if handler.get("auto_decode", True) and handler.get("content_type") == "application/json":
|
|
600
|
+
try:
|
|
601
|
+
body = loads(body)
|
|
602
|
+
except Exception:
|
|
603
|
+
pass
|
|
560
604
|
response = await wait_for(
|
|
561
605
|
handler["handle"](body),
|
|
562
606
|
timeout=handler["response_timeout"],
|
|
@@ -572,7 +616,7 @@ class AsyncChannel:
|
|
|
572
616
|
type="normal",
|
|
573
617
|
),
|
|
574
618
|
)
|
|
575
|
-
not self.auto_ack and not self._channel.basic_ack(
|
|
619
|
+
not self.auto_ack and self._channel.is_open and not self._channel.basic_ack(
|
|
576
620
|
basic_deliver.delivery_tag
|
|
577
621
|
)
|
|
578
622
|
|
|
@@ -599,7 +643,7 @@ class AsyncChannel:
|
|
|
599
643
|
type="error",
|
|
600
644
|
),
|
|
601
645
|
)
|
|
602
|
-
return not self.auto_ack and self._channel.basic_nack(
|
|
646
|
+
return not self.auto_ack and self._channel.is_open and self._channel.basic_nack(
|
|
603
647
|
basic_deliver.delivery_tag, requeue=False
|
|
604
648
|
)
|
|
605
649
|
|
|
@@ -615,7 +659,7 @@ class AsyncChannel:
|
|
|
615
659
|
type="error",
|
|
616
660
|
),
|
|
617
661
|
)
|
|
618
|
-
not self.auto_ack and self._channel.basic_nack(
|
|
662
|
+
not self.auto_ack and self._channel.is_open and self._channel.basic_nack(
|
|
619
663
|
basic_deliver.delivery_tag, requeue=False
|
|
620
664
|
)
|
|
621
665
|
|