amqp-client-python 0.2.0.post1__tar.gz → 0.2.1.dev1__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.
Files changed (128) hide show
  1. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/PKG-INFO +1 -2
  2. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/amqp_client_python/__init__.py +11 -0
  3. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/amqp_client_python/domain/models/__init__.py +6 -0
  4. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/amqp_client_python/domain/utils/__init__.py +5 -0
  5. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/amqp_client_python/event/__init__.py +9 -0
  6. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/amqp_client_python/exceptions/__init__.py +12 -0
  7. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/amqp_client_python/rabbitmq/__init__.py +15 -1
  8. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/amqp_client_python/rabbitmq/async_channel.py +13 -7
  9. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/amqp_client_python/rabbitmq/async_connection.py +57 -27
  10. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/pyproject.toml +1 -2
  11. amqp_client_python-0.2.1.dev1/tests/integration/async_eventbus/auto_recovery_test.py +167 -0
  12. amqp_client_python-0.2.1.dev1/tests/unit/eventbus/test_auto_recovery.py +64 -0
  13. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/.env.example +0 -0
  14. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/.flake8 +0 -0
  15. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/.github/workflows/lint.yml +0 -0
  16. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/.github/workflows/publish.yml +0 -0
  17. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/.github/workflows/test.yml +0 -0
  18. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/.github/workflows/type.yml +0 -0
  19. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/.gitignore +0 -0
  20. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/.pre-commit-config.yaml +0 -0
  21. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/.readthedocs.yaml +0 -0
  22. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/.vscode/settings.json +0 -0
  23. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/CODE_OF_CONDUCT.md +0 -0
  24. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/LICENSE +0 -0
  25. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/README.md +0 -0
  26. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/amqp_client_python/domain/models/config.py +0 -0
  27. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/amqp_client_python/domain/models/options.py +0 -0
  28. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/amqp_client_python/domain/models/ssl_options.py +0 -0
  29. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/amqp_client_python/domain/utils/connection_type.py +0 -0
  30. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/amqp_client_python/domain/utils/types.py +0 -0
  31. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/amqp_client_python/event/async_provider_handler.py +0 -0
  32. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/amqp_client_python/event/async_subscriber_handler.py +0 -0
  33. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/amqp_client_python/event/event.py +0 -0
  34. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/amqp_client_python/event/integration_event.py +0 -0
  35. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/amqp_client_python/event/integration_event_handler.py +0 -0
  36. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/amqp_client_python/event/provider_handler.py +0 -0
  37. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/amqp_client_python/event/subscriber_handler.py +0 -0
  38. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/amqp_client_python/exceptions/auto_reconnect_exception.py +0 -0
  39. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/amqp_client_python/exceptions/blocking_exception.py +0 -0
  40. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/amqp_client_python/exceptions/eventbus_exception.py +0 -0
  41. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/amqp_client_python/exceptions/nack_exception.py +0 -0
  42. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/amqp_client_python/exceptions/publish_timeout_exception.py +0 -0
  43. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/amqp_client_python/exceptions/response_timeout_exception.py +0 -0
  44. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/amqp_client_python/exceptions/rpc_provider_exception.py +0 -0
  45. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/amqp_client_python/exceptions/thread_unsafe_exception.py +0 -0
  46. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/amqp_client_python/exceptions/timeout_exception.py +0 -0
  47. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/amqp_client_python/py.typed +0 -0
  48. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/amqp_client_python/rabbitmq/async_channel_factory.py +0 -0
  49. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/amqp_client_python/rabbitmq/async_connection_factory.py +0 -0
  50. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/amqp_client_python/rabbitmq/async_eventbus_rabbitmq.py +0 -0
  51. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/amqp_client_python/rabbitmq/channel_factory_rabbitmq.py +0 -0
  52. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/amqp_client_python/rabbitmq/channel_rabbitmq.py +0 -0
  53. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/amqp_client_python/rabbitmq/connection_factory_rabbitmq.py +0 -0
  54. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/amqp_client_python/rabbitmq/connection_rabbitmq.py +0 -0
  55. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/amqp_client_python/rabbitmq/eventbus_rabbitmq.py +0 -0
  56. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/amqp_client_python/rabbitmq/eventbus_wrapper_rabbitmq.py +0 -0
  57. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/amqp_client_python/rabbitmq/ioloop_factory.py +0 -0
  58. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/amqp_client_python/signals.py +0 -0
  59. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/create-self-signed-certs.sh +0 -0
  60. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/dev_requirements.txt +0 -0
  61. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/docker-compose.yml +0 -0
  62. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/docs/API/domain/config.md +0 -0
  63. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/docs/API/domain/options.md +0 -0
  64. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/docs/API/domain/ssl_options.md +0 -0
  65. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/docs/API/rabbitmq/async_eventbus.md +0 -0
  66. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/docs/contribute.md +0 -0
  67. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/docs/index.md +0 -0
  68. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/docs/stylesheets/extra.css +0 -0
  69. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/docs/templates/cards.html +0 -0
  70. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/docs/templates/installation.md +0 -0
  71. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/docs/templates/sponsors.md +0 -0
  72. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/docs/templates/user_guide.md +0 -0
  73. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/docs/tutorial.md +0 -0
  74. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/examples/__init__.py +0 -0
  75. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/examples/async_case.py +0 -0
  76. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/examples/default.py +0 -0
  77. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/examples/multprocess.py +0 -0
  78. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/examples/publish.py +0 -0
  79. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/examples/rpc_publish.py +0 -0
  80. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/examples/rpc_subscribe.py +0 -0
  81. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/examples/sanic_test.py +0 -0
  82. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/examples/ssl_connection.py +0 -0
  83. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/examples/subscribe.py +0 -0
  84. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/examples/sync_case.py +0 -0
  85. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/examples/sync_wrapper_case.py +0 -0
  86. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/examples/thread.py +0 -0
  87. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/external_config/rabbitmq/rabbitmq.conf +0 -0
  88. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/mkdocs.yml +0 -0
  89. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/mypy.ini +0 -0
  90. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/tests/__init__.py +0 -0
  91. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/tests/integration/__init__.py +0 -0
  92. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/tests/integration/async_eventbus/__init__.py +0 -0
  93. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/tests/integration/async_eventbus/conftest.py +0 -0
  94. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/tests/integration/async_eventbus/rpc_provider_test.py +0 -0
  95. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/tests/integration/async_eventbus/signals_test.py +0 -0
  96. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/tests/integration/async_eventbus/ssl_test.py +0 -0
  97. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/tests/integration/async_eventbus/subscribe_test.py +0 -0
  98. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/tests/integration/async_eventbus_wrapper/__init__.py +0 -0
  99. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/tests/integration/async_eventbus_wrapper/conftest.py +0 -0
  100. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/tests/integration/async_eventbus_wrapper/rpc_provider_test.py +0 -0
  101. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/tests/integration/async_eventbus_wrapper/signals_test.py +0 -0
  102. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/tests/integration/async_eventbus_wrapper/ssl_test.py +0 -0
  103. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/tests/integration/async_eventbus_wrapper/subscribe_test.py +0 -0
  104. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/tests/integration/eventbus/__init__.py +0 -0
  105. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/tests/integration/eventbus/conftest.py +0 -0
  106. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/tests/integration/eventbus/publish_test.py +0 -0
  107. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/tests/integration/eventbus/rpc_publish_test.py +0 -0
  108. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/tests/integration/eventbus/rpc_subscribe_test.py +0 -0
  109. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/tests/integration/eventbus/subscribe_test.py +0 -0
  110. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/tests/unit/__init__.py +0 -0
  111. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/tests/unit/channels/test_async_channel.py +0 -0
  112. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/tests/unit/channels/test_async_rpc_client.py +0 -0
  113. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/tests/unit/channels/test_channel_factory.py +0 -0
  114. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/tests/unit/channels/test_publish.py +0 -0
  115. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/tests/unit/channels/test_rpc_subscribe.py +0 -0
  116. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/tests/unit/channels/test_subscribe.py +0 -0
  117. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/tests/unit/config/test_uri.py +0 -0
  118. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/tests/unit/conftest.py +0 -0
  119. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/tests/unit/eventbus/default.py +0 -0
  120. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/tests/unit/eventbus/test_async_eventbus.py +0 -0
  121. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/tests/unit/eventbus/test_async_eventbus_provide_resource.py +0 -0
  122. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/tests/unit/eventbus/test_async_eventbus_publish.py +0 -0
  123. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/tests/unit/eventbus/test_async_eventbus_rpc_client.py +0 -0
  124. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/tests/unit/eventbus/test_async_eventbus_subscribe.py +0 -0
  125. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/tests/unit/utils/ca.pem +0 -0
  126. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/tests/unit/utils/rabbitmq_cert.pem +0 -0
  127. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/tests/unit/utils/rabbitmq_key.pem +0 -0
  128. {amqp_client_python-0.2.0.post1 → amqp_client_python-0.2.1.dev1}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: amqp-client-python
3
- Version: 0.2.0.post1
3
+ Version: 0.2.1.dev1
4
4
  Summary: Python AMQP Client Library
5
5
  Project-URL: Homepage, https://github.com/nutes-uepb/amqp-client-python
6
6
  Project-URL: Repository, https://github.com/nutes-uepb/amqp-client-python
@@ -17,7 +17,6 @@ Classifier: License :: OSI Approved :: Apache Software License
17
17
  Classifier: Natural Language :: English
18
18
  Classifier: Operating System :: OS Independent
19
19
  Classifier: Programming Language :: Python :: 3
20
- Classifier: Programming Language :: Python :: 3.8
21
20
  Classifier: Programming Language :: Python :: 3.9
22
21
  Classifier: Programming Language :: Python :: 3.10
23
22
  Classifier: Programming Language :: Python :: 3.11
@@ -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
+ ]
@@ -1,3 +1,9 @@
1
1
  from .config import Config
2
2
  from .options import Options
3
3
  from .ssl_options import SSLOptions
4
+
5
+ __all__ = [
6
+ "Config",
7
+ "Options",
8
+ "SSLOptions",
9
+ ]
@@ -1,2 +1,7 @@
1
1
  from .connection_type import ConnectionType
2
2
  from .types import HandlerType
3
+
4
+ __all__ = [
5
+ "ConnectionType",
6
+ "HandlerType",
7
+ ]
@@ -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, ConnectionType
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 (
@@ -63,9 +64,9 @@ class AsyncChannel:
63
64
  def is_open(self) -> Optional[bool]:
64
65
  return self._channel and self._channel.is_open
65
66
 
66
- def open(self, connection: AsyncioConnection, callbacks=[]):
67
+ def open(self, connection: AsyncioConnection, callbacks=None):
67
68
  if not self.is_open:
68
- self.callbacks = callbacks
69
+ self.callbacks = callbacks if callbacks is not None else deque()
69
70
  self._connection = connection
70
71
  self.ioloop = connection.ioloop
71
72
  self._channel = self.channel_factory.create_channel(
@@ -87,10 +88,15 @@ class AsyncChannel:
87
88
  self.ioloop.create_task(self.process_callbacks()) # type: ignore
88
89
 
89
90
  async def process_callbacks(self):
90
- for callback, future in self.callbacks:
91
- result = await callback()
92
- if not future.done():
93
- future.set_result(result)
91
+ while self.callbacks:
92
+ callback, future = self.callbacks.popleft()
93
+ try:
94
+ result = await callback()
95
+ if not future.done():
96
+ future.set_result(result)
97
+ except Exception as exc:
98
+ if not future.done():
99
+ future.set_exception(exc)
94
100
 
95
101
  def add_on_channel_close_callback(self):
96
102
  """This method tells pika to call the on_channel_closed method if
@@ -302,7 +308,7 @@ class AsyncChannel:
302
308
  if self.rpc_publisher_started:
303
309
  return True
304
310
  if self.rpc_publisher_starting:
305
- return await self.rpc_consumer_future
311
+ return await self.rpc_publisher_future
306
312
  self.rpc_publisher_starting = True
307
313
  self.rpc_publisher_future = self.ioloop.create_future()
308
314
 
@@ -1,4 +1,6 @@
1
- from typing import Optional, Callable, Awaitable, Tuple, Dict, List, Any
1
+ from collections import deque
2
+ from typing import Optional, Callable, Awaitable, Tuple, Dict, Any
3
+
2
4
  from .async_connection_factory import AsyncConnectionFactoryRabbitMQ, AsyncioConnection
3
5
  from .async_channel import AsyncChannel
4
6
  from ..exceptions import AutoReconnectException
@@ -60,7 +62,7 @@ class AsyncConnection:
60
62
  self.openning = False
61
63
  self.reconnecting = False
62
64
  self.reconnect_delay = 1
63
- self.callbacks: List[Tuple[Callable, Future]] = []
65
+ self.callbacks: deque[Tuple[Callable, Future]] = deque()
64
66
  self.type = connection_type
65
67
  self.backup: Dict[str, Dict[str, Any]] = {
66
68
  "exchange": {},
@@ -87,6 +89,7 @@ class AsyncConnection:
87
89
  if not self.ioloop:
88
90
  self.ioloop = get_event_loop()
89
91
  self.openning = True
92
+ self._closing = False
90
93
  self._connection = self.connection_factory.create_connection(
91
94
  uri=uri,
92
95
  on_connection_open=self.on_connection_open,
@@ -129,6 +132,33 @@ class AsyncConnection:
129
132
  signal=self.signal,
130
133
  )
131
134
  self._channel.publisher_confirms = self.publisher_confirms
135
+
136
+ if self.reconnecting or self.backup["subscribe"] or self.backup["rpc_subscribe"]:
137
+ async def recorvery():
138
+ for routing_key in list(self.backup["subscribe"].keys()):
139
+ params = self.backup["subscribe"][routing_key]
140
+ await self.subscribe(
141
+ params["queue_name"],
142
+ params["exchange_name"],
143
+ routing_key,
144
+ params["callback"],
145
+ params["timeout"],
146
+ )
147
+ for routing_key in list(self.backup["rpc_subscribe"].keys()):
148
+ params = self.backup["rpc_subscribe"][routing_key]
149
+ await self.rpc_subscribe(
150
+ params["queue_name"],
151
+ params["exchange_name"],
152
+ routing_key,
153
+ params["callback"],
154
+ params["timeout"],
155
+ )
156
+
157
+ future: Future = Future(loop=self.ioloop)
158
+ self.callbacks.append((recorvery, future))
159
+ self.reconnect_delay = 1
160
+ self.reconnecting = False
161
+
132
162
  self._channel.open(self._connection, self.callbacks)
133
163
 
134
164
  def on_connection_open_error(self, _unused_connection, err):
@@ -193,30 +223,30 @@ class AsyncConnection:
193
223
  )
194
224
  self.reconnect_delay += 1
195
225
  else:
196
-
197
- async def recorvery():
198
- for routing_key in self.backup["subscribe"]:
199
- params = self.backup["subscribe"][routing_key]
200
- await self.subscribe(
201
- params["queue_name"],
202
- params["exchange_name"],
203
- routing_key,
204
- params["callback"],
205
- params["timeout"],
206
- )
207
- for routing_key in self.backup["rpc_subscribe"]:
208
- params = self.backup["rpc_subscribe"][routing_key]
209
- await self.rpc_subscribe(
210
- params["queue_name"],
211
- params["exchange_name"],
212
- routing_key,
213
- params["callback"],
214
- params["timeout"],
215
- )
216
-
217
- self.ioloop.create_task(self.add_callback(recorvery)) # type: ignore
218
- self.reconnect_delay = 1
219
- self.reconnecting = False
226
+ if self.reconnecting:
227
+ async def recorvery():
228
+ for routing_key in list(self.backup["subscribe"].keys()):
229
+ params = self.backup["subscribe"][routing_key]
230
+ await self.subscribe(
231
+ params["queue_name"],
232
+ params["exchange_name"],
233
+ routing_key,
234
+ params["callback"],
235
+ params["timeout"],
236
+ )
237
+ for routing_key in list(self.backup["rpc_subscribe"].keys()):
238
+ params = self.backup["rpc_subscribe"][routing_key]
239
+ await self.rpc_subscribe(
240
+ params["queue_name"],
241
+ params["exchange_name"],
242
+ routing_key,
243
+ params["callback"],
244
+ params["timeout"],
245
+ )
246
+
247
+ self.reconnect_delay = 1
248
+ self.reconnecting = False
249
+ self.ioloop.create_task(self.add_callback(recorvery)) # type: ignore
220
250
 
221
251
  @property
222
252
  def is_open(self) -> Optional[bool]:
@@ -464,7 +494,7 @@ class AsyncConnection:
464
494
  >>> result = await connection.add_callback(my_operation, 10.0)
465
495
  """
466
496
  try:
467
- if self.is_open and self._channel.is_open:
497
+ if self.is_open and self._channel and self._channel.is_open:
468
498
  return await callback()
469
499
  else:
470
500
  future: Future = Future(loop=self.ioloop)
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "amqp-client-python"
3
- version = "0.2.0.post1"
3
+ version = "0.2.1.dev.1"
4
4
  description = "Python AMQP Client Library"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.9,<4.0"
@@ -19,7 +19,6 @@ classifiers = [
19
19
  "Operating System :: OS Independent",
20
20
  "License :: OSI Approved :: Apache Software License",
21
21
  "Programming Language :: Python :: 3",
22
- "Programming Language :: Python :: 3.8",
23
22
  "Programming Language :: Python :: 3.9",
24
23
  "Programming Language :: Python :: 3.10",
25
24
  "Programming Language :: Python :: 3.11",
@@ -0,0 +1,167 @@
1
+ import pytest
2
+ from amqp_client_python import AsyncEventbusRabbitMQ
3
+ from asyncio import Future, BaseEventLoop, sleep
4
+ from uuid import uuid4
5
+
6
+
7
+ @pytest.mark.asyncio_cooperative
8
+ async def test_rpc_provider_auto_recovery_on_connection_drop(async_eventbus: AsyncEventbusRabbitMQ, loop: BaseEventLoop):
9
+ """
10
+ Test that an RPC provider automatically recovers its consumers when the RabbitMQ connection is forcibly closed,
11
+ allowing subsequent RPC calls to succeed without manual restarts.
12
+ """
13
+ expected_result = "response_after_recovery"
14
+ future_count = 0
15
+
16
+ async def handle(body):
17
+ nonlocal future_count
18
+ future_count += 1
19
+ return expected_result
20
+
21
+ routing_key = f"rk_recovery_{str(uuid4())[:8]}"
22
+
23
+ # 1. Register RPC provider
24
+ await async_eventbus.provide_resource(routing_key, handle, timeout=10)
25
+ await sleep(1)
26
+
27
+ # 2. First RPC request before disconnect
28
+ res1 = await async_eventbus.rpc_client(
29
+ async_eventbus.config.options.rpc_exchange_name,
30
+ routing_key,
31
+ ["request_1"],
32
+ timeout=10,
33
+ )
34
+ assert res1 == expected_result.encode()
35
+ assert future_count == 1
36
+
37
+ # 3. Simulate sudden RabbitMQ connection drop by closing the underlying connection
38
+ rpc_conn = async_eventbus._rpc_server_connection
39
+ if rpc_conn._connection:
40
+ rpc_conn._connection.close()
41
+
42
+ # 4. Wait for auto-reconnection and consumer re-registration
43
+ await sleep(3)
44
+
45
+ # 5. Second RPC request after auto-recovery
46
+ res2 = await async_eventbus.rpc_client(
47
+ async_eventbus.config.options.rpc_exchange_name,
48
+ routing_key,
49
+ ["request_2"],
50
+ timeout=10,
51
+ )
52
+ assert res2 == expected_result.encode()
53
+ assert future_count == 2
54
+
55
+
56
+ @pytest.mark.asyncio_cooperative
57
+ async def test_subscribe_auto_recovery_on_connection_drop(async_eventbus: AsyncEventbusRabbitMQ, loop: BaseEventLoop):
58
+ """
59
+ Test that a pub/sub subscriber automatically recovers its consumers when the RabbitMQ connection is forcibly closed.
60
+ """
61
+ received_messages = []
62
+
63
+ async def handle(body):
64
+ received_messages.append(body)
65
+
66
+ exchange_name = "ex_subscribe_recovery"
67
+ routing_key = f"rk_sub_{str(uuid4())[:8]}"
68
+
69
+ # 1. Register subscriber
70
+ await async_eventbus.subscribe(exchange_name, routing_key, handle, timeout=10)
71
+ await sleep(1)
72
+
73
+ # 2. Publish message 1
74
+ await async_eventbus.publish(exchange_name, routing_key, ["msg_1"])
75
+ await sleep(1)
76
+ assert len(received_messages) == 1
77
+
78
+ # 3. Force connection drop on sub_connection
79
+ sub_conn = async_eventbus._sub_connection
80
+ if sub_conn._connection:
81
+ sub_conn._connection.close()
82
+
83
+ # 4. Wait for auto-reconnection
84
+ await sleep(3)
85
+
86
+ # 5. Publish message 2 after auto-recovery
87
+ await async_eventbus.publish(exchange_name, routing_key, ["msg_2"])
88
+ await sleep(1)
89
+ assert len(received_messages) == 2
90
+ assert received_messages == [["msg_1"], ["msg_2"]]
91
+
92
+
93
+ @pytest.mark.asyncio_cooperative
94
+ async def test_multiple_consecutive_drops_auto_recovery(async_eventbus: AsyncEventbusRabbitMQ, loop: BaseEventLoop):
95
+ """
96
+ Edge Case Test: Verify auto-recovery works reliably over multiple consecutive connection drops (network flapping).
97
+ """
98
+ call_count = 0
99
+
100
+ async def handle(body):
101
+ nonlocal call_count
102
+ call_count += 1
103
+ return f"resp_{call_count}"
104
+
105
+ routing_key = f"rk_flapping_{str(uuid4())[:8]}"
106
+ await async_eventbus.provide_resource(routing_key, handle, timeout=10)
107
+ await sleep(1)
108
+
109
+ # Perform 3 consecutive drops and verify recovery after each
110
+ for i in range(1, 4):
111
+ # Force close connection
112
+ rpc_conn = async_eventbus._rpc_server_connection
113
+ if rpc_conn._connection:
114
+ rpc_conn._connection.close()
115
+
116
+ # Wait for auto-recovery
117
+ await sleep(3)
118
+
119
+ # Make RPC call after recovery
120
+ res = await async_eventbus.rpc_client(
121
+ async_eventbus.config.options.rpc_exchange_name,
122
+ routing_key,
123
+ [f"req_{i}"],
124
+ timeout=10,
125
+ )
126
+ assert res == f"resp_{i}".encode()
127
+
128
+ assert call_count == 3
129
+
130
+
131
+ @pytest.mark.asyncio_cooperative
132
+ async def test_multiple_handlers_simultaneous_recovery(async_eventbus: AsyncEventbusRabbitMQ, loop: BaseEventLoop):
133
+ """
134
+ Edge Case Test: Verify that multiple RPC handlers registered on the same connection all recover concurrently without channel collision.
135
+ """
136
+ keys = [f"rk_multi_{i}_{str(uuid4())[:6]}" for i in range(3)]
137
+ counts = {k: 0 for k in keys}
138
+
139
+ def make_handler(k):
140
+ async def handler(body):
141
+ counts[k] += 1
142
+ return f"ok_{k}"
143
+ return handler
144
+
145
+ # Register 3 handlers concurrently
146
+ for k in keys:
147
+ await async_eventbus.provide_resource(k, make_handler(k), timeout=10)
148
+ await sleep(1)
149
+
150
+ # Force connection drop
151
+ rpc_conn = async_eventbus._rpc_server_connection
152
+ if rpc_conn._connection:
153
+ rpc_conn._connection.close()
154
+
155
+ # Wait for auto-recovery
156
+ await sleep(3)
157
+
158
+ # Verify all 3 handlers recovered and respond correctly
159
+ for k in keys:
160
+ res = await async_eventbus.rpc_client(
161
+ async_eventbus.config.options.rpc_exchange_name,
162
+ k,
163
+ ["payload"],
164
+ timeout=10,
165
+ )
166
+ assert res == f"ok_{k}".encode()
167
+ assert counts[k] == 1
@@ -0,0 +1,64 @@
1
+ import pytest
2
+ from collections import deque
3
+ from unittest.mock import MagicMock, AsyncMock, patch
4
+ from asyncio import get_event_loop
5
+ from amqp_client_python.rabbitmq.async_channel import AsyncChannel
6
+ from amqp_client_python.rabbitmq.async_connection import AsyncConnection
7
+
8
+
9
+ @pytest.mark.asyncio_cooperative
10
+ async def test_start_rpc_publisher_concurrency_fix():
11
+ """Verify start_rpc_publisher awaits rpc_publisher_future when starting, avoiding rpc_consumer_future bug."""
12
+ channel = AsyncChannel()
13
+ channel.ioloop = get_event_loop()
14
+ channel.rpc_publisher_starting = True
15
+ channel.rpc_publisher_future = channel.ioloop.create_future()
16
+ channel.rpc_publisher_future.set_result(True)
17
+
18
+ result = await channel.start_rpc_publisher()
19
+ assert result is True
20
+
21
+
22
+ @pytest.mark.asyncio_cooperative
23
+ async def test_process_callbacks_clears_list():
24
+ """Verify process_callbacks clears the callbacks list so tasks are not executed twice."""
25
+ channel = AsyncChannel()
26
+ called = False
27
+
28
+ async def dummy_callback():
29
+ nonlocal called
30
+ called = True
31
+ return True
32
+
33
+ loop = get_event_loop()
34
+ future = loop.create_future()
35
+ channel.callbacks = deque([(dummy_callback, future)])
36
+
37
+ await channel.process_callbacks()
38
+ assert called is True
39
+ assert len(channel.callbacks) == 0
40
+ assert future.done()
41
+ assert future.result() is True
42
+
43
+
44
+ @pytest.mark.asyncio_cooperative
45
+ async def test_auto_recovery_on_connection_open():
46
+ """Verify on_connection_open enqueues recovery callback when reconnecting is True."""
47
+ loop = get_event_loop()
48
+ conn = AsyncConnection(ioloop=loop)
49
+ conn.reconnecting = True
50
+ conn.backup["rpc_subscribe"]["test_key"] = {
51
+ "queue_name": "q_test",
52
+ "exchange_name": "ex_test",
53
+ "callback": AsyncMock(),
54
+ "timeout": 10,
55
+ }
56
+
57
+ mock_connection = MagicMock()
58
+ conn.connection_factory.create_connection = MagicMock(return_value=mock_connection)
59
+
60
+ with patch.object(AsyncChannel, "open") as mock_channel_open:
61
+ conn.on_connection_open(mock_connection)
62
+ assert conn.reconnecting is False
63
+ assert len(conn.callbacks) == 1
64
+ mock_channel_open.assert_called_once()